并行程序设计四部曲 ---第一部 导论

来源:互联网 发布:java抢票软件开发原理 编辑:程序博客网 时间:2024/04/27 23:53

并行计算基础

从1986-2002 CPU 以性能每年50%的速度增长,但从2002年开始已经降到每年20%了。免费的午餐已经结束了。所以我们需要从程序设计上想办法。
在21世纪第一个10年中,用空气冷却的集成电路的散热能力已经到达极限了。

任务并行:
将需要解决的问题分配到各个核上执行。
数据并行:
将待解决问题所需的处理的数据分配给各个核,每个核在分配到的数据集上执行大致相似的操作。
学习并行:

我们将学习C的三种并行接口(MPI,POSIX,OpenMp),以及两种并行系统(共享内存系统,分布式内存系统)。

在共享内存系统中,各个核能够共享计算机的内存,理论上每个核都能读写内存的所有区域。因此能够通过检测和更新共享内存中的数据来协调各个核。
分布式内存系统中,每个核都拥有自己的私有内存,核之间的通信是显示的,必须使用类似于网络中发消息的机制。

pthread 和 OpenMP 是为共享内存系统的编程设计的,他们提供访问共享内存的机制,而MPI 是为分布式内存系统设计的,它提供发消息的机制。

并行硬件和并行软件

1.串行系统

典型的冯.诺伊曼体系就是一个串行的体系结构,主存,中央处理单元,以及互连结构。
冯.诺伊曼瓶颈就是数据从内存到CPU之间的流动,毕竟这效率低下。

2.解决冯.诺伊曼瓶颈的方法

缓存方式:
详情可以看我内存管理器(十九)中对高速缓存的介绍
虚拟存储器:
Cache 使得CPU快速访问成为可能。但是,当一个程序运行一个大型的程序,或者程序需要访问大型数据集,那么主存是放不下的。这种情况在多任务操作系统中时常发生。因为在多任务的操作系统中,即使主存非常大,许多运行中的程序必须共享可用的主存。此外,这种共享必须确保每个程序的数据和指令能被保护,不会被其他程序访问。利用虚拟存储器,使得主存可以作为辅助的缓存。它通过在主存中存放当前执行程序需要用到的部分,利用时间和空间局部性;那些暂时用不到的部分也存储在辅助存储空间中,就是交换空间中。当然访问内存会比访问交换空间慢几十万倍。而且每次回写都在页大小级别。毕竟访问磁盘太费时费力。

低层次并行:
指令级并行:有两种方法来实现指令级并行:1.流水线2.多发射
流水线:将功能单元分阶段安排,总的来说,k个阶段的流水线不可能达到K倍的性能提高,一般取决于最慢的那个单元
多发射:指让多条指令同时启动,通过复制功能单元来同时执行程序中不同指令。如果功能单元是在编译时调度的,则称该多发射系统使用静态多发射,如果是在运行时间调度的,则称之为发射系统的动态多发射。一个支持的动态多发射的处理器称之为超标量。
当能够使用多发射技术其中又有一个重要的功能称之为预测。预测会猜测下一步的指令,当然如果猜测就回退机制。预测一般是由编译器决定的。
3.硬件多线程
线程级并行:
(Thread-Level Parallelism,TLP)尝试通过同时执行不同的程序来提高并行性。于ILP相比,TLP提供的是粗粒度的并行性,即同时执行程序的基本单元比细粒度的程序单元更大或者更粗。
硬件多线程为程序提供了一种机制,使得当前执行的任务被阻塞时,系统能够继续执行其他而有用的工作。
在细粒度的程序中,处理器在每条指令执行完成后切换线程,从而跳过被阻塞的线程。但是缺点是在执行一个很长一段指令的线程在执行每条指令的时候都需要等,粗粒度多线程为了避免这个问题,值切换那些需要长时间才能完成的操作(例如从主存中加载东西)而被阻塞的线程。这种机制确实不需要立即切换线程,但是在短阻塞的事后可能会发生延迟。
同步多线程(SMT):
它通过允许多个线程同时使用多个功能单元来利用超标量处理器的性能。如果我们能确定出优先的线程,那么也能够在一定的程度上减轻线程减速的问题。
4.并行硬件
有一些已经出现的硬件为并行提供了一些帮助。
SIMD系统
根据Flynn分类方法,我们对计算机的种类进行了分类。
典型的冯。诺伊曼计算机体系是单指令单数据流系统,因为它一次执行一条指令,一次存取一个数据项。
单指令多数据流就是SIMD 系统。顾名思义,单指令就是指令就一个,多数据就是多种数据,合起来就是多个不同数据一直以一个指令来运算。从结构上说可以理解为一个控制单元和多个ALU 计算单元的核,其实就是GPU的架构。
例如
for(i = 0;i < n;i++){
x[i]+=y[i];
}
一般CPU的算法是按照步长为1来顺序计算,但是SIMD的并行算法大是:如果有4个ALU计算单元,0~3,4~7这样同时4个4个的计算,提高效率。但是最后一组就会出现ALU空闲的情况,这也是个大BUG。在经典的SIMD中ALU没有有指令存储器,所以所有ALU必须是同步的操作。
现在SIMD系统被用来生产向量处理器,现在GPU,核多CPU也应用了SIMD系统相关知识。

向量处理器
向量处理器核普通CPU 的全标量操作有很大的不同,主要表现在它以下方面:
向量寄存器,向量化和流水化的功能单元,向量指令,交叉存储器,步长存储器访问和硬件散射/聚集。
MIMD系统(多指令多数据)
系统支持同时多个指令流在多个数据流上操作。因此,MIMD长包含一组完全独立的处理单元或核,每个处理单元都有自己的控制单元和ALU。此外它还是异步的。
MIMD系统主要有两种类型:
1.共享内存系统
2.分布式内存系统
共享内存系统(Linux就是这样)其中还包含UMA一致性访问,NUMA非一致性访问。
UMA:每个核在访问内存中任何一个区域时间爱你都相同
NUMA:访问与核直接相连的那块内存区域比访问其他内存区域要快的多,因为访问其他区域需要另一块芯片。

分布式内存系统
最广泛的分布式内存系统称为集群。他们是由一组商品化系统组成的,通过商品化网络连接,实际上这些系统的节点都是一个或者多个多核系统共享的内存系统,为了将这种系统与纯粹的分布式内存系统分开,这种系统称为混合系统。通过网络连接起来的系统我们称之为异构的。

互联网络:

这里所说的网路并不是以太网这种网络,而是计算机核与存储器之间的链接网络。
现阶段有总线,较差矩阵开关,分布式内存互连(直接互连,环面互连)。衡量“同时通信的链路数目“或者”链接性“的一个标准是等分宽度。其实就是允许同时通信的次数,一般按照最坏的情况估计。
计算等分宽度:
将节点分成大小相等的两个部分,并确定在两个等分之间发生多少通信,或者至少需要移除多少数目的链路才能使两个等分不再通信。
如果一个正方形的二维环面网格有 p = q^2 个节点。则其等分宽度最多是2q = 2sqrt(p).一个正方行二维环面网格的等分宽度就是2sqrt(p).
带宽:
是指其传输数据的速度。
最理想的直接互连网络全相连网络,就是每个交换器与每一个其他的交换器直接链接。它的等分宽度是p^2/4 ,但是为了节点数目很多的系统构建这样的网络是不合算的。
超立方体:
这是一种用于实际系统中高度互连的直接互联网络。
还有间接互连,交叉开关矩阵,omega网络等。
Cache 一致性
首先Cache 是由CPU系统硬件来管理的,程序员不能对它进行控制,假如有如下语句
x = 2
核0 核1
y0 = x; y1 = 3*x;
x = 7 ; /////
//// z1 = 2*x;
首先不管CPU执行优先顺序,我们确定y0 = 2,y1 = 6.但是z1是多少?这是一个问题。这是一个不可预测的问题,但是本质上却并不关写策略什么事情。如果直写那么主存会更新x = 7 ,但是核1的Cache 无关,但是如果用写回,核0Cache中的新值对核1是不可用的。所以在多个核的系统中,各个核的Cache 存储相同变量的副本,当一个处理器更新Cache中该变量的副本时,其他处理器都会知道并且更新,这就是Cache一致性问题。
保证Cache 一致性的方法:
1.监听Cache一致性协议
这种协议基于总线,当核0更新Cache 时在总线上直接广播,那么其他的核都可以收到。
注意:互联网络不一定都是总线,只要能广播就可以,必须在写回和只写的系统上都可以运行。
2.目录的Cache 一致性协议
这里通过一个叫作目录的数据结构来解决一致性问题,每个核/内存 目录负责存储一部分的目录,这部分的目录标识局部内存对应高速缓存行的状态,因此当一个高速缓存行被读入后,与这个高速缓存行对应的目录项就会更新。当需要更新变量,就会查询目录,并将所有的包含该变量高速缓存行作为非法。

负载均衡:
需要在进程/线程之间平均分配任务从而是每个进程/线程获得大致相等的工作量。
并行软件:
对与并行软件我们需要关心的东西:
1。将任务在进程线程之间分配
2。这个分配可以使得每个进程线程获得大致相等的工作量
3。这个分配可以使得需要的通信量是最小的
4。安排进程线程之间的通信
5。安排进程线程之间的同步

关于输入输出的一些问题
首先我们的C语言是串行语言,当多个进程尝试访问stdin,stdout,stderr 时是未知的情况,当运行多个进程调用scanf是情况也是不可预测的,所以我们需要根据以下的规则优化输入输出在并行程序中的使用。
为了能部分的解决这些问题我们需要使用这些假设并且遵循它。
在分布式内存程序中只有0进程能访问stdin.在共享内存程序中,只有主线程或者线程0能访问stdin.
在分布式内存和共享内存系统中,所有进程线程都能访问stdout,stderr
调试程序时允许多个进程线程能写stdout
只有一个进程/线程能访问一个除了stdin/stdout或者stderr外的文件。每个进程或者线程能够打开自己私有的文件进行读写但是没有两个进程线程能打开相同的文件。
调试程序在输出结果时应当显示当前输出的进程线程号。

 

评价一个并行程序的标准

线性加速比 = T串行/p核数

程序的加速比 :
S = T并行/T串行
线性加速比是很难达到的,这里有一个并行程序的示例

P 1 2 4 8 16

S 1.0 1.9 3.6 6.5 10.8

E=S/p 1.0 0.95 0.90 0.81 0.68

可以看到并不是随着核数的增加,加速比越接近线性加速比的,E称为并行程序的效率。

当然一个并行程序的效率还取决于问题的规模,一般情况下问题规模越小,加速比和效率降低,一个问题的规模越大,加速比和效率越高。
考虑到程序本身的开销有如下公式:

T并行= T串行/(P + T开销)
T串行我们一般认为是一个并行系统的一个核运行的效率,并非是最强CPU运行出的时间。

阿姆达尔定律

大致上,除非一个串行程序的执行几乎全部都并行化,否则不论有多少可利用的核,通过并行化所产生的加速比都会是受限制的。
假设一个程序的T串行 = 20 秒,并且可以并行化90%,那么T串行 = 18/p + 2;S = 20/(18/p + 2);
,当增加到很大时 S <= 10,所以说即使有10000个核,也得不到比10 更大的加速比。

然而,我们并不需要过分注重这个定律,首先这个定律本身就没有注意到问题规模的问题,其次我们如果能的到5~10 的加速比已经不错了,尤其是我们不需要很费力的重写并行化程序的时候。
可扩展性

当问题的规模扩展速率和增加的线程进程速率一致时,效率E会保持不变的程序我们称之为可扩展的。

并行化程序的一种方法:Foster 方法

1.划分,将一个问题划分成多个小问题。
2.通信,确定上一步后确定通信功能。
3.凝聚或聚合,将第一步所确定的任务与通信结合成更大的任务。
4.分配,将上述的任务分配进进城线程中。负载均衡使通信量最小化。

 

参考资料《并行程序设计导论》

查看原文:http://zmrlinux.com/2016/01/19/%e5%b9%b6%e8%a1%8c%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1%e5%9b%9b%e9%83%a8%e6%9b%b2-%e7%ac%ac%e4%b8%80%e9%83%a8-%e5%af%bc%e8%ae%ba/

0 0