我有一个基于BiCCG(共轭梯度)的矩阵求解器,该矩阵求解器也考虑了周期性。碰巧的情况是,该实现需要大量计算,并且由于存在依赖性问题,因此循环没有自动矢量化。我进行了一些探索,似乎红黑高斯seidel算法比普通版本(也有类似的依赖问题)更有效地并行化。
可以更改此循环/算法以便对其进行有效的矢量化吗?
// FORWARD
#pragma omp for schedule(static, nx/NTt)
for (i=1; i<=nx; i++) for (j=1; j<=ny; j++) for (k=1; k<=nz; k++)
{
dummy = res_sparse_s[i][j][k];
dummy -= COEFF[i][j][k][7] * RLL[i-1][j][k];
if (PeriodicBoundaryX && i==nx)dummy -= COEFF[i][j][k][8] * RLL[1 ][j][k];
dummy -= COEFF[i][j][k][2] * RLL[i][j-1][k];
if (PeriodicBoundaryY && j==ny) dummy -= COEFF[i][j][k][3] * RLL[i][1 ][k];
dummy -= COEFF[i][j][k][4] * RLL[i][j][k-1];
if (PeriodicBoundaryZ && k==nz) dummy -= COEFF[i][j][k][5] * RLL[i][j][1 ];
RLL[i][j][k] = dummy / h_sparse_s[i][j][k];
}
P.S。任何迭代i,j,k的RLL通过变量哑元在i-1,j-1和k-1处合并更新的“ RLL”。现在,也仅使用伪指令schedule(static, nx/NTt)
在x方向上分解循环,其中NTt只是可用线程数的宏。可以使用伪指令collapse
在所有方向对其进行分解吗?
-------主要编辑-------------------------- 遵循Ajay的回答,这里是一个最小的工作示例
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<omp.h>
typedef double lr;
#define nx 4
#define ny 4
#define nz 4
void
print3dmatrix(double a[nx+2][ny+2][nz+2])
{
for(int i=1; i<= nx; i++) {
for(int j=1; j<= ny; j++) {
for(int k=1; k<= nz; k++) {
printf("%f ", a[i][j][k]);
}
printf("\n");
}
printf("\n");
}
}
int
main()
{
double a[nx+2][ny+2][nz+2];
double b[nx+2][ny+2][nz+2];
srand(3461833726);
// matrix filling
// b is just a copy of a
for(int i=0; i< nx+2; i++) for(int j=0; j< ny+2; j++) for(int k=0; k< nz+2; k++)
{
a[i][j][k] = rand() % 5;
b[i][j][k] = a[i][j][k];
}
// loop 1
//#pragma omp parallel for num_threads(1)
for(int i=1; i<= nx; i++) for(int j=1; j<= ny; j++) for(int k=1; k<= nz; k++)
{
a[i][j][k] = -1*a[i-1][j][k] - 1*a[i][j-1][k] -1 * a[i][j][k-1] + 4 * a[i][j][k];
}
print3dmatrix(a);
printf("******************************\n");
// loop 2
//#pragma omp parallel for num_threads(1)
for(int i=1; i<= nx; i++)
for(int j=1; j<= ny; j++)
// #pragma omp simd
for(int m=j+1; m<= j+nz; m++)
{
b[i][j][m-j] = -1*b[i-1][j][m-j] - 1*b[i][j-1][m-j] -1 * b[i][j][m-j-1] + 4 * b[i][j][m-j];
}
print3dmatrix(b);
printf("=========================\n");
return 0;
}
主要观察结果
#pragma omp simd
强制执行最内层循环的矢量化,则会失败。答案 0 :(得分:2)
这是循环携带依赖性的经典问题。您的每次迭代都依赖于其他一些迭代(已经完成),因此,可以对其进行调度的唯一方法是依次进行。
但这只是因为循环的编写方式。
您提到R[i][j][k]
取决于R[i-1][j][k]
,R[i][j-1][k]
,R[i][j][k-1]
的计算。我在这里看到三个依赖-
我希望这种表示是直观的。
对于您目前的情况,依赖性1)和2)没问题,因为0
中有k
,而1
/ {{中有i
1}},这意味着该迭代不依赖于k的先前迭代来完成这两个相关性。
问题是由于3)。由于j
中有一个1
,因此每个迭代都取决于它的上一个迭代。如果我们能够以某种方式在k
/ >0
中输入数字i
,我们一定会成功。循环歪斜变换使我们可以完全相同。
3D示例有点难以理解。因此,让我们看一下使用j
和i
的2D示例。
假设-j
取决于R[i][j]
和R[i-1][j]
。我们有同样的问题。
如果我们必须在图片中表示它,它看起来像这样-
R[i][j-1]
在此图中,每个点代表一个迭代. <- . <- .
| |
v v
. <- . <- .
| |
v v
. . .
,并且从每个点开始的箭头指向其依赖的迭代。显而易见,为什么我们不能在这里并行化最内部的循环。
但是假设我们的偏斜为-
(i,j)
如果您绘制与上图中相同的箭头(我无法在ASCII艺术作品中绘制对角箭头)。
您将看到所有箭头都指向下方,即它们至少向下迭代,这意味着您可以并行化水平循环。
现在说您的新循环尺寸为 .
/|
/ |
. .
/| /|
/ | / |
. . .
/|
/ |
. .
.
(外循环)和y
(内循环),
您的原始变量x
,i
将是
j
和j = x
您的循环主体因此变为-
i = x - y
其中for ( y = 0; y < j_max + i_max; y++)
for ( x = 0; x < j_max; x++)
R_dash[y][x] = R_dash[y-1][x-1] + R_dash[y-1][x];
是倾斜域,并且与R_dash
一对一映射
您将看到R
和R_dash[y-1][x-1]
都将在y的某些先前迭代中进行计算。因此,您可以完全并行化x循环。
此处应用的转换为
R_dash[y-1][x]
。
您可以类似地针对3个维度进行计算。
要进一步了解仿射变换如何工作以及如何将其用于引入并行性,请参见lecture notes。