Principles of Parallel Algorithm Design(并行算法设计原则)
并行算法讲述的是怎么用多处理器来解决问题。设计一个并行算法包括以下某些步骤:
- Identifying portions of the work that can be performed concurrently.
- Mapping the concurrent pieces of work onto multiple processes running in parallel.
- Distributing the input, output, and intermediate data associated with the program.
- Managing accesses to data shared by multiple processors.
- Synchronizing the processors at various stages of the parallel program execution.
这里的讨论不考虑自动并行化工具和编译器的作用,这里讨论的并行算法是程序员的责任。
任务依赖图( taskdependency graph):有向无环图
粒度(granularity)
1. 细粒度(fine-grained)将任务分解成大量细小的任务。
2. 粗粒度(coarse-grained)少量的大任务。
并发度(degree of concurrency):任一时刻可同时执行的最大任务数称为最大并发度。并发度依赖于任务图的形状,同样的粒度(基于同样的分解)不能保证同样的并发度。
平均并发度:总工作量与关键路径长度(任何一对起始节点与终止节点之间的最长有向路径)的比就是平均并发度。较短的关键路径有利于达到较高的并发度。
我们不能获得无限的加速比(串行运行时间与并行运行时间的比率):
1. 有限的粒度和并发度。
2. 不同处理器上任务之间的交互(某个任务的输出是另一个任务的输入,访问同一个变量副本)。
任务交互图(task-interaction graph.)
稀疏矩阵-向量相乘(n * n 和 n * 1):每个任务负责矩阵的一行还有对应向量的一位,只有该行某个位置处值不为0,才用进行相乘操作。这时候就需要拿到其他任务拥有的对应向量值。这样就得到任务交互图。
进程(执行任务的处理代理,不同于操作系统中的进程)和映射(给进程分派任务的机制)
任务依赖图和任务交互图对一个好的映射起着很大的作用。更有意义的映射是把由边相连的任务映射到同一个进程上。
进程(表示并行算法或程序,可能有多个任务,一般是第二个任务依赖第一个任务)与处理器(实际执行计算的硬件部分)都有一一对应的关系
分解技术
- 递归分解:快速排序,求一个数组的最小值
-
数据分解:
- 以划分输出数据为基础(矩阵相乘)
- 划分输入数据
- 划分输入和输出数据
- 划分中间结果数据
拥有者-计算规则:以划分输入或输出数据为基础的分解也称为拥有者-计算规则
- 以划分输出数据为基础(矩阵相乘)
探测性分解:15迷宫问题。只要一个任务找到答案就可以终止了,执行的操作数可能比串行多也可能比串行少。
推测性分解:switch分支,在得到结果前先分别执行。时间少于串行计算时间,但有操作浪费。因此可以在只有最可能发生的分支执行提前计算。
任务和交互的特点
任务特性
- 任务产生:静态产生和动态产生(实际任务和任务依赖图不能提前获得,如快速排序的递归分解)
- 任务的大小:是否均匀
- 任务大小是否已知
- 与任务相关的数据大小
任务间交互的特征
- 静态(交互的时间和任务集是已知的)与动态
- 有规则(图像浓淡处理问题)与无规则(稀疏矩阵问题)
- 只读与读写
- 单向(只有一方发起交互并且丝毫不影响另一方)与双向
负载平衡的映射技术
任务到进程的有效映射必须达到下面的两个目标:
1. 减少进程间彼此交互的总时间
2. 减少一些进程繁忙而其他一些进程空闲的总时间
3. 这两个目标通常都会相互冲突。
分配均衡的聚合负载到每一个进程是减少进程空闲的必要条件,但不是充分条件。因为由分解得到的任务并非总能同时执行,并且有的进程还要执行任务间的交互花费时间。好的映射方法必须确保在并行算法的各个执行阶段都能在进程间保持很好的计算和交互平衡。
静态映射 :在算法执行前将任务分配给进程
-
以数据划分为基础的映射
块分配:一般分配的维度越高,更有助于减少不同进程间的交互(比如矩阵相乘,A和B都划分为小矩阵相乘比A单独取一行来的好,单独取一行需要访问整个B矩阵)
循环分配和块循环分配:如果一个矩阵不同元素的计算量不同,那么块分配将会引起负载不平衡。块循环分配将n * n矩阵划分为每个块大小为n/(ap)的ap组。块bi会被分配到进程P(i % p)。
如果a = n / p, 那么这种分配称为循环分配,在一维中每一块只有一行。可以有很好的负载平衡,但是交互太多。a = 1的时候就变成了块分配。高维块循环分配通常更可取。
随机块分配:块被均匀的和随机的分配到进程中。
图划分:稀疏结构,尽量分配相邻格网点到同一进程。
以任务划分为基础的映射:基于划分任务依赖图,使得交互开销更小。
分级映射:纯粹建立在任务依赖图上的映射可能会遇到负载不平衡,或并发度不够。将树的顶部任务再分解为多个任务。
动态映射:在算法执行期间在进程间分配任务
集中式方案:更容易实施,但可能更加限制可扩展性。大量访问主进程会导致瓶颈。主进程管理任务池。
分布式方案:分布在多个进程中,在运行时通过交换任务来保持负载平衡。
包含交互开销的方法
- 最大化数据本地性(重用中间数据)
- 最小化数据交换量
- 最小化数据交换频率
- 最小化争用与热点
- 计算与交互重叠
- 复制数据或计算
- 使用最优聚合交互操作
- 一些交互与另一些交互的重叠
并行算法模型
- 数据并行模型:每一个任务对不同数据进行相同的操作
- 任务图模型:从任务依赖图开始
- 工作池模型:动态映射任务到进程保持负载平衡
- 主-从模型:一个进程生成工作给其他工作进程
- 流水线模型或生产者消费者模型:数据流通过一串进程传递,每个进程执行一个任务
- 混合模型:多个模型混合