弗分类法
根据弗分类法,计算机结构主要分为
- SIMD----单指令、多数据
- MIMD---多指令、多数据
- SISD----单指令、单数据
- MISD---多指令、单数据
一般的串行程序中为SISD,即在单核CPU下任何时间和地点只有一个指令处理一个数据,其所谓的多线程实际上是采用时分方法,以使人感觉是在同时运行
而现在大多数多核计算机一般为MIMD,操作系统复杂多线程或进程的调度,将线程或进程分布在不同的CPU上,每个线程都具有独立的指令和数据
SIMD是对SISD提出的一个改进,虽然只有一个指令但是能够一次性处理多个数据,典型代表Arm的Neon指令集,X86 SSE指令集,在硬件能够批量处理数据
MISD比较少见,与MIMD相比,性价比并不高、
与上述应对的,英伟达在GPU的实现被称为SIMT(single Instruction Mutiple Thread)单指令多线程,与CPU相比每个线程的指令(相当于kernel)不是固定的,是由开发人员编写kernel制定时间工程内容。
并行计算
并行计算是相对与串行程序来讲,能够同时处理多个任务,提高计算效率,比如:
设 有数组A 和 数组 B,数组中A的每个元素乘以数组B的每个元素,串行程序的编写思路:
for (int index = 0; index < ArraySize; index++)
{
Result[inxex] = A[index] * B[index];
}
假设A和B的数组非常的大,超过了千万次 甚至亿次,即使只是两个元素相乘,计算时间也非常耗时,为了提高计算效率,使用线程增加实际的并行数:
Task 1:
for (int index = 0; index < ArraySize/2; index++)
{
Result[inxex] = A[index] * B[index];
}
Task 2:
for (int index = ArraySize/2; index < ArraySize; index++)
{
Result[inxex] = A[index] * B[index];
}
计算效率比之前提高了越一半,继续增加并行处理能力:
当增加到实际并行处理能力 和数组大小一样,那每个任务的实际处理指令仅为
Result[inxex] = A[index] * B[index];
其效率相比之前有了根本性的提高。
所谓并行计算就是将串行中的指令或数据进行分解使之能够同时处理更多的数据,以此来减少串行单个计算问题所需要的实际。实际上能够并行处理的数目与使用的硬件有关,比如CPU或者GPU的core数目。GPU的硬件整个设计理念都是与并行计算密不可分的,通过增加硬件核数来增加并行能力,以此来提高效率。
并发性
并发性的核心即内涵,对于一个特定的问题,无需考虑用哪种并行计算来求解,而只需关注求解方法中哪些操作是可以并行的,以及数据之间的相关性进行解耦
数据的易并行即将每一个输出数据都表示成其其他数据或者步骤无关即数据之间没有太多的关联性,这是并发执行的核心,再常见的操作中,比如矩阵乘法,其每个矩阵的元素的输出结果与其他元素的输出结果无关,没有相关性,所有在进行并行计算中 矩阵的相关操作很容易实现并行计算。
在有些算法中可能有一个阶段不是“易并行”吗,但某一步或某几步还是能够用实现并行计算,也是有很大框架,那么该算法的最大瓶颈就是串行执行部分。
并行处理的类型
常见的并行处理的类型主要有两种基于任务或基于数据。
基于任务的并行处理
所谓任务的并行处理,有一种前提各个进程不同、无关的,如下图所示:
有6个任务,任务之前的相互之间是独立的,没有关联,在单核处理器中一般都需要按照顺序依次进行,其运行的总时间为6个任务的总时间。而在一个3核CPU中,可以通过将不同的任务分布到不同的CPU中以提高程序效率。在分配方法一中,分别将1和3、2和4以及5和6上分布到三个核上吗,此时耗时取决于执行时间最长的CPU即1和3 分布的任务。对该方案进一步改进,将4和5以及2三个执行时间比较短的任务分布在一个CPU上, 任务1单独分布在一个CPU上,这样总体时间相对来说进一步减小。
任务并行的最大难处,需要开发人员根据实际需求进行手工调整,其运行时间取决于耗时最长的CPU。
但是在实际开发过程中,上述任务直接相互独立这种理想场景很难见到,一般任务直接都会涉及到交互,最坏的一种情况是本任务的输入都依赖于上一个任务的输出,例如:
有1、2、3和4共四个任务,每个任务的输出都依赖于前面的输入,整个任务的处理完全是一个串行程序,上述场景在我们实际的开发中经常遇到,在实际开发中经常根据整个业务流程处理,将其切割成不同的模块来进行处理,模块之前的输入和输出相互存在依赖关心,整个业务流程完全处理完成需要的时间非常长,两个输出的间隔时间为(1+2+3+4).处理完整个业务流程过程中,任务1处理完成之后交给任务2,任务2处理完成交给任务3... 如此以来任务1在处理完成之后到接收到下一个任务数据输入时,其等待时间为2、3、4的总和时间这段时间完全处于忙等待时间,极大浪费的CPU等硬件资源。
为了解决串行任务中的并行问题,以及提高整个资源的使用率,采用流水线并行处理(pipeline parallelism)技术,在1处理完成之后,将数据交接给任务2时,任务1继续接收下一个数据,以此类推构成一个流水线形式,如下图 :
经过并行流水线改进之后,两个输出的时间间隔为流水线中耗时任务最长的任务2 ,相比于之前的效率有了很大的提高。经过流水线的改造之后,其短板取决于耗时最长的任务,那么对最长的任务继续进行分解,增加整个流水线的长度如下:
对任务2进行分解,分解成两个相等的任务,这样整个流水线的每个任务耗时比较均衡,两个输出时间间隔为一个任务的处理耗时,理论上对于串行程序实现并行改进,通过增加流水线处理,可以提高整个程序的并行能力,并提高资源利用率。
基于数据的并行
随着过去几十年的芯片的发展,所能获得的计算能力不断增长,但是,跟不上计算能力增长步伐的是数据的访问。基于数据并行的思路是首先关注数据及其所需要的变换,而不是待执行的任务。
比如一个对4个不同且无关的等长数组分别进行不同变换的例子,假设有4个CPU和一个带有4个SM的GPU(SM在GPU内部主要负责线程块的调度),在基于任务的并行处理中,将产生4个线程或进程来完成任务,如果要在在GPU上,则将使用4个线程块,并把每个数组的地址分别送给1个块,基于任务或者数据变换来考虑。
而基于数据的分解则是将第一个数组分成4个数据块,CPU中的一个核或者GPU中的1一个SM分别处理数组中的1数据块。处理完成完毕后,再按照相同的方式处理剩下的3个数组。对采用GPU处理而言,将产生4个内核程序,每个内核程序包含4个或者更多的数组块。对问题的这种并行分解是再考虑数据后再考虑变换的思路下进行的。
如上图所示,就是将数组A的数据进行分解,然后再考虑数据结果的转换。
缓存一致性问题
在进行基于数据分解过程中,CPU和GPU最大的差别就是缓存一致性问题。 在CPU内部由于为了加快数据的存取速度,一般都有一块缓存,用于临时存储常用的数据,这样做能够减少对内存的读写加快数据操作速度,现在CPU中做三级缓存也是非常常见,如果在一个多核的系统中,对一个内存的写操作需要通知所有核以及对应的各级别的缓存,来同步更新数组。因此无论何时,所有处理器看到的内存视图时完全一样的。随着现在处理器的CPU核数越来越多,这个“通知”的开销迅速增大,使得“缓存一致性”成为限制一个处理器中核数不能太多的一个重要因素。“缓存一致”系统中最坏的情况是,一个内存写操作会强迫每个核的缓存都进行更新,进而每个核都要对相邻的内存单元进行写操作。
相比之下,非“缓存一致”系统不会自动地更新其他核缓存。它需要开发人员写清楚各个处理器核输出得各自不同的目标区域。从程序的视角看,这支持一个核仅负责一个输出或者一个小的输出集。通常,CPU遵循“缓存一致”原则,而GPU则不是。故GPU能够扩展到一个芯片内具有大数量的核心(流处理器簇)。
来源:CSDN
作者:Huo的藏经阁
链接:https://blog.csdn.net/weixin_42730667/article/details/104345760