第二章:并行硬件和并行软件
背景知识
冯诺依曼结构:内存与CPU分开,CPU分为控制单元和算术逻辑单元,内存的速度限制了CPU的速度
进程、多任务和线程:没啥好说的,操作系统都有
对冯·诺依曼结构的改进
使用cache机制,加宽总线使得一次能够取多个指令或数据,将具有局部性的指令或数据存储在cache中。
cache映射:全相联(任意一个位置都可以),直接映射(只能有唯一一个位置),n路组相联(一组有n个,映射到唯一一个组里的n个位置)
虚拟存储器:多个程序运行的数据和指令内存不一定放得下所以要用到虚拟内存,即用虚拟的页提供一个大内存,利用页表将虚拟地址转化为物理地址,但是增加了一次访问页表的时间,可以使用TLB来缓存一部分页表
指令级并行:通过让多个处理器部件或者功能单元同时执行指令来提高处理器的性能。流水线是将功能单元分阶段安排,让处理器处理不同的阶段;多发射是让多条指令同时启动。
流水线:多个处理器处理同一任务的不同阶段。
多发射:多个处理器同时处理能够同时处理的(不相关)指令。分为静态多发射和动态多发射。要用到预测来提前执行指令。支持动态多发射的处理器成为超标量处理器。
硬件多线程:当前任务阻塞时执行其他任务
细粒度:每条指令执行完后切换线程;粗粒度:较长阻塞时切换线程
同步多线程:多个线程同时使用多个功能单元来利用超标量处理器的性能。
并行硬件
概念:并行硬件仅限于对程序员可见的硬件,即可以通过修改代码来开发并行性,多发射和流水线对程序员不可见。
SIMD系统
Flynn分类法根据同时管理的指令流数和数据流数进行分类。经典的冯诺依曼结构属于SISD,而单指令多数据流SIMD是一种并行系统,在多个ALU上对数据执行相同的指令,属于数据并行
向量处理器:向量处理器用来处理数组或数据向量,使用SIMD并行
图形处理单元(GPU):GPU使用图形处理流水行来处理实时图形,流水线的许多阶段可编程,这种可编程通过着色函数体现,对临近元素使用着色函数会导致相同的指令流,因此现在的GPU都使用SIMD并行,为了避免内存访问的延迟,GPU严重依赖硬件多线程(阻塞时挂起访问其他线程)。GPU不是纯粹的SIMD,一个核上的多个ALU使用SIMD,但是GPU也有多个核。
MIMD系统
概述:多指令多数据流支持多个指令流在多个数据流上进行操作,因此它不靠多个ALU来并行,而是依靠完全独立的处理单元或核,MIMD系统通常是异步的,即各个处理单元之间互不影响。MIMD主要有两种类型,共享内存系统和分布式内存系统
共享内存系统:一组自治的处理器通过互联网络与内存相互连接。使用一个或多个多核处理器
一致内存访问(UMA):所有处理器直接连接到主存,访问主存的时间相同
非一致内存访问(NUMA):每个处理器连接到一块内存,通过处理器内置的特殊硬件访问内存中的其他块,访问自己的那块内存速度比其他块的内存快很多。
分布式内存系统:每个处理器都有私有的内存空间,通过互联网络通信
最广泛使用的分布式内存系统成为集群,由一系列系统组成,通过网络连接。
互联网络
概述:互联网络也对并行系统的效率有很多影响
共享内存互联网络:分为总线和交叉开关矩阵。总线便宜但节点多了容易冲突,交叉开关矩阵冲突数少但是贵。
分布式内存互联网络
直接互联:每个交换器与一个处理器-内存对直接互联。有环,环面网格,全相联(理想状况,太贵了),超立方体等互联方式,等分宽度,等分带宽以及造价(链路数)是考虑的因素。
间接互联:每个交换器不与处理器直接连接,每个处理器有一个输入链路和输出链路,通过交换网络进行通信,交换网络有交叉开关矩阵和omega网络,前者造价高但效率高,后者造价低但效率低,评价标准相同。
延迟和带宽
在本书中,延迟指数据开始传输到数据开始接受的时间,带宽指接收数据的速率
cache一致性
概述:在多核系统中,各个核的cache存储相同变量的副本,当一个核更新一个变量副本的时候,其他核应该知道该变量已更新。有两种方法:监听cache一致性协议和基于目录的cache一致性协议。
监听cache的一致性协议:当一个核更新变量的副本时,会将这个更新通过互联网络或总线广播出去,其他核能够知道有这个更新。
基于目录的cache一致性协议:每个核/内存对都有一个目录,存储部分的变量状态,当核读取/更新属于自己目录那部分的变量的时候会更新目录,如果要更新不属于自己目录的变量,那么会去与有该变量的核交涉得到变量状态。
伪共享
因为cache每次取不是取一个变量,往往取一个高速缓存行,所以当高速缓存行一个变量的状态发生改变时,cpu会把整个高速缓存行的变量的状态都改变,所以明明有些变量没有变,但其他核访问这些变量的时候发现状态改变还是会重新从主存中读取,降低了效率,即有些变量明明不是共享变量,但因为与其他变量处于同一个高速缓冲行表现为共享变量,所以叫做伪共享。
并行软件
概述:共享内存往往是一个进程派生出多个线程运行,分布式系统往往是多个进程在不同的处理器上运行。
注意事项:我们主要讨论MIMD系统,又主要关心其中的SPMD程序,SPMD既可以任务并行也可以数据并行。
进程或线程的协调:编写并行程序要考虑负载均衡、同步、通信。
共享内存
在共享内存系统中,变量可以是私有的,也可以是共享的,而共享内存系统的通信是通过共享变量来进行的,即通信是隐式的。
动态线程和静态线程:动态线程是等有工作了主线程派生出线程来工作,静态线程是主线程先把所有工作线程派生出来(如线程池)。
不确定性:由于存在执行顺序的差别,程序最终执行的结果有可能不确定,可以通过锁、忙等待、信号量、监视器、事务内存等方法解决。
线程安全性:很多时候并行程序调用串行程序的函数不会出问题,但是有时候会导致出错,例如函数中有静态变量。
分布式内存
概述:分布式内存中每个核有自己的私有内存,通常使用的是消息传递的API。
消息传递:消息传递的API至少要提供发送和接收函数,通过进程之间的序号进行标识。消息传递是很底层的,要管理很多细节。
单向通信:消息传递一般需要两个进程的参与,一个发送一个接收,而在单向通信中单个处理器调用一个函数可以完成通信(具体见书)。
划分全局地址空间:PGAS提供了一些共享内存程序的机制以方便共享内存系统在分布式节点上应用。
混合系统编程:在单个节点上使用共享内存API,在多个节点使用分布式内存API通信,不过编程很困难。
输入与输出
在本书中开发的程序输入输出规模不大,可以直接使用C语言的输入输出函数,但是C语言的输入输出函数是为串行程序设计的,所以我们要对输入输出做一些具体的规定来在并行程序中使用这些函数。
性能
加速比和效率
概念:加速比:S=T串行/T并行;效率:E = S/p
理想情况下每个核平分任务,如果有p个核,则加速比为p,E=1.
随着问题规模的增大,S和E也增大。
阿姆达尔定律:当一个程序的必须串行化部分比例为r,则加速比最多趋近于1/r。
可拓展性
在并行程序中,如果我们增加一个程序的进程/线程数,同时输入规模也以相应增长率增加,该程序的效率一直不变,则称该程序可拓展。
计时
我们需要对我们编写的程序的某一部分进行计时,可以利用API提供的计时函数,一般我们报告的是所有线程中的最长时间。又由于多次测试时间可能会不同,我们一般报告最短时间。
并行程序设计
可以使用Foster方法设计并行程序:划分任务;通信;凝聚或聚合;分配。
编写和运行并行程序
可以通过IDE来编写和运行并行程序。在规模较小的共享内存系统中,运行单一的操作系统副本,在可用核之间进行调度,同时输入输出,也可以用批处理调度器来请求核和指定输入输出位置;在分布式内存系统中,由一个宿主计算机分配工作。
假设
在本书中,我们做出如下假设:使用同步MIMD系统,程序是SPMD的,各个核结构相同,单个核最多运行一个进程或线程,使用静态进程或线程,使用C语言的并行拓展,使用shell命令行来编译运行程序。