不幸的是,循环间数据依赖性限制了您在嵌套循环中可以获得的并行度。
您可以使用具有依赖关系的任务,这将是最简单的方法。 OpenMP 运行时库将负责调度,您只需关注您的算法。另一个好处是在任何循环结束时都没有同步,而只是在代码的依赖部分之间。
#pragma omp parallel
#pragma omp single
for (int k = 0; k < k2; k += BLOCK_SIZE) {
for (int j = 0; j < j2; j += BLOCK_SIZE) {
for (int i = 0; i < i2; i += BLOCK_SIZE) {
#pragma omp task depend (in: AX(i-1,j,k),AX(i,j-1,k),AX(i,j,k-1)) \
depend (out: AX(i,j,k))
// your code here
}
}
}
任务有时比并行循环更昂贵(取决于工作负载和同步粒度),因此另一种选择是wavefront parallelization 模式,它基本上转换了迭代空间,以便内部循环中的元素在每个之间是独立的其他(所以你可以在那里使用并行)。
如果您想采用任何一种方法,我强烈建议您将算法转换为阻塞算法:展开您的 3 嵌套循环以分两个阶段进行计算:
- 在固定大小的块/立方体之间进行迭代(我们将您的新归纳变量称为
ii、jj 和kk)。
- 对于每个块,调用循环的原始串行版本。
阻塞的目的是增加并行部分的粒度,使并行化开销不那么明显。
这里是阻塞部分的一些伪代码:
#define min(a,b) ((a)<(b)?(a):(b))
// Inter block iterations
for (int kk = 0; kk < k2; kk += BLOCK_SIZE) {
for (int jj = 0; jj < j2; jj += BLOCK_SIZE) {
for (int ii = 0; ii < i2; ii += BLOCK_SIZE) {
// Intra block iterations
for (int k = kk; k < min(k2,k+BLOCK_SIZE); k++) {
for (int j = jj; j < min(j2,j+BLOCK_SIZE); j++) {
for (int i = ii; i < min(i2,i+BLOCK_SIZE); i++) {
// Your code goes here
}
}
}
}
}
}
在波前并行化的情况下,最后一步是将外部循环(块间迭代)转换为波前,以便您迭代彼此之间不依赖的元素。在 3D 迭代空间中,它基本上是一个从 (0,0,0) 前进到 (i2,j2,k2) 的对角平面。类似于下图中以红色突出显示的那个。
我将举一个二维波前的例子,因为它更容易理解。
#define min(a,b) ((a)<(b)?(a):(b))
#pragma omp parallel
for (int d = 1; d < i2+j2; d++ ) {
int i = min(d,i2) - 1;
int j = 0;
// Iterations in the inner loop are independent
// Implicit thread barrier (synchronization) at the end of the loop
#pragma omp for
for ( ; i >= 0 && j < min(d,j2); i--, j++) {
// your code here
}
}