编译原理(龙书)第十一章 并行和局部优化学习笔记
11.1 基本概念
Amdahl定律
如果 $\mathcal{f}$ 是被并行化代码的比率,并且如果并行化版本在一个有 $\mathcal{p}$ 个处理器的机器上运行,且没有任何通信或者并行化开销,那么此时的加速比是:
$$\frac{1}{(1 - \mathcal{f}) + (\mathcal{f} / \mathcal{p})}$$
循环层次上的并行性
循环是并行化的主要目标,找到循环中相互独立的迭代,将这些迭代分配给多个处理器。
例子:
//代码1,原始代码
for (i = 0; i < n; ++i>) {
z[i] = x[i] - y[i];
z[i] = z[i] * z[i];
}
//代码2, 并行代码
//循环中的迭代平均分配给各个处理器,第p个处理器被分配执行第p组循环
b = ceil(n / M) // n 为循环次数,M为处理器个数,ceil() 为向上取整
for (int i = b * p; i < min(n, b * (p + 1)); ++i) {
'''
i < min(n, b * (p + 1)) 是为了判断,
如果此时进程i的编号小于循环个数,则其他处理器不会进行操作
'''
z[i] = x[i] - y[i];
z[i] = z[i] * z[i];
}
上述的并行代码是一个SPMD(Single Program Multiple Data, 单程序多数据)
循环层次的并行
将循环中的互相独立的迭代分配给不同的处理器。
任务层次上的迭代
将不同函数的调用过着两个独立的循环分配给不同的处理器。但是如果要对程序进行并行性处理,任务层次不会得到太多的关注,因为对没个程序来说,相互独立的任务数是固定的,不能随着数据大小的增加而增加。而循环中的迭代次数会因为数据量的增加而增加。
数据局部性
C++中数组元素是按行进行存储的。
//代码3,按行将数组置零
for (i = 0; i < n; ++i) {
for (j = 0; j < n; ++j) {
z[i][j] = 0;
}
}
//代码4,并行按行将数组置零
b = ceil(n / M);
for (i = b * p; i < min(n, b * (p + 1)); ++i) {
for (j = 0; j < n; ++j) {
z[i][j] = 0
}
}
仿射变换理论概述
循环优化问题空间
- 迭代空间(iteration space):
在一些计算过程中动态执行示例的集合,也就是各个循环下标的取值的组合。 - 数据空间(data space):
被访问的数组元素的集合。 - 处理器空间(processor space):
系统中处理器的集合。通常情况下,这些树立起使用整数或者整数向量进行编号。
优化问题的输入与输出
- 输入:
各个迭代被执行的串行顺序以及一个仿射的数组访问函数(比如,X[i, j + 1])。这个函数描述了迭代空间中的哪个实例访问数据空间中的哪个元素 - 输出:
用仿射函数表示,定义了没个处理器在什么时候做什么事。- 什么事情:
使用一个仿射函数将原迭代空间中的实例映射到各个处理器上。 - 什么时候执行:
使用一个仿射函数将迭代空间中的实例映射成为一个新的顺序。
- 什么事情:
具体分析例子,龙书P494
11.3 迭代空间
迭代空间
作为外层循环下标的一个仿射函数,每个循环的上下界都定义了一个不等式,它把空间分成了两半:对应循环迭代部分(即正的半空间)和不对应迭代的部分(即负的半空间)。所有的线性不等式的交(逻辑AND)表示这些正的半空间的交集,该交集定义了一个凸多面体(convex polyhedron)。这个多面体就是这个循环嵌套结构的迭代空间。
迭代空间是数组中被代码访问到的部分,与数据空间不一定完全相同。
投影
把表示迭代空间的凸多面体投影到改空间中对应于较外层循环的维度上。可以用投影的方式生成循环界限。
Fourier-Motzkin 消除算法
输入:一个带有变量x1, x2, $\cdots$, xn的多面体S。也就是说,S是关于变量xi的一组线性约束。一个给定的变量xm是被指定需要消除的变量。
输出:一个关于x1, x2, $\cdots$, xm - 1, $\cdots$, xm + 1, $\cdots$, xn(即除了xm以外的所有S的变量)的多面体S'。S'是S到除第m个维度之外的所有维度的投影。
方法:龙书P504 - 505
11.4 仿射的数组下标
仿射访问
如果下列条件同时成立,我们就说一个循环中的一个数组访问是仿射的。
- 该循环的上下界被表示为外围循环变量和符合常量的仿射表达式。
- 该数组的每个维度的下标也是外围循环变量和符号常量的仿射表达式。
仿射数组访问的例子有Z[i], Z[i + j + 1], Z[0],但是Z[i * j]和Z[n * j]不是仿射访问。
非仿射访问
- 涉及稀疏矩阵的程序。(但是如果稀疏情况是规律的,比如一个带状矩阵,即非零元素都集中在对角线周围,数组的访问仍可能是仿射的。)
- 线性化数组。即在编译时多维数组的某一维度可能未知。
j = n;
for (i = 0; i <= n; ++i) {
Z[j] = 0;
j = j + 2;
}
改写成:
\\ 对Z数组的访问变成仿射访问
j = n;
for (i = 0; i <= n; ++i) {
Z[n + 2 * i] = 0;
}
11.5 数据复用
对于局部性优化,我们希望识别出访问相同数据或相同高速缓存线的迭代组合。
- 静态访问:
访问指令本身。 - 动态访问:
执行该循环嵌套结构时该语句的多次迭代。 - 时间复用:复用指向完全相同的位置。
- 空间复用:复用指向同一个高速缓存线。
数据复用的类型
数据复用可以分为自复用和组复用两种。
自复用
定义:复用同样的数据的多个迭代源于同一个静态访问。
如果一个静态访问所指向的数据具有k个维度,且这个循环嵌套在一个深度为d(d > k)的循环结构中,那么同一个数据可以被复用nd - k次。
矩阵的零空间
给定矩阵 $\mathcal{A}$ $\in$ $\mathcal{R}$ m $\times$ n,那么矩阵的零空间定义为:
如果X ∈ $\mathcal{R}$ n $\times$ 1,且有 $\mathcal{A}$ X = 0 那么所有这样的X组成的线性空间就是矩阵A的零空间。
矩阵的零度
矩阵零空间的维度称为矩阵的零度(nullity)。
秩-零定理(The Rank-Nullity Theorem)
给定矩阵$\mathcal{A}$ $\in$ $\mathcal{R}$ m $\times$ n,我们有
$$\mathcal{rank(A)} + \mathcal{nullity(A)} = n$$若零数为0,则说明所有的迭代都指向不同的位置,不存在复用的可能。
零空间的基本向量是矩阵F作为系数矩阵对应齐次方程的解。
自空间复用
空间复用的分析依赖于矩阵的数据布局。C语言的矩阵是按行存放的,Fortran语言的矩阵是按列存放的。
发现和利用自空间复用的技巧是不考虑系数矩阵F中的最后一行。如果得到的截短后的矩阵的秩小于循环嵌套结构的深度,那么我们就可以确保最内层循环只改变数组的最后下标的值,从而保证空间局部性。
例子分析:龙书P513
组复用
复用同样的数据的多个迭代源不源于同一个静态访问。
我们只在同一个循环中具有相同系数矩阵的数组访问之间计算组复用。
给定两个动态访问 Fi1 + f1 和 Fi2 + f2,它们复用相同数据的条件是:
$$
\begin{aligned}
Fi_1 + f_1 = Fi_2 + f_2;\
F(i_1 - i_2) = (f_1 - f_2)
\end{aligned}
$$

浙公网安备 33010602011771号