具有数据依赖性的for循环的矢量化

时间:2018-07-29 13:36:37

标签: c multithreading algorithm optimization vectorization

我有一个基于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;
}

主要观察结果

  1. 矩阵a填充了0到5之间的随机数,循环1是未经转换的原始循环,而循环2是经过转换的循环
  2. 已转换的循环已倾斜,以消除依赖关系。
  3. 如果不使用openmp并行化运行,则运算矩阵a和b相同
  4. 如果部署了开放式mp,则答案会发生变化(由于种族条件而定)[循环不存在Parallellisable,无论将杂物放在何处]
  5. 如果使用#pragma omp simd强制执行最内层循环的矢量化,则会失败。

1 个答案:

答案 0 :(得分:2)

这是循环携带依赖性的经典问题。您的每次迭代都依赖于其他一些迭代(已经完成),因此,可以对其进行调度的唯一方法是依次进行。

但这只是因为循环的编写方式。

您提到R[i][j][k]取决于R[i-1][j][k]R[i][j-1][k]R[i][j][k-1]的计算。我在这里看到三个依赖-

  1. [1、0、0]
  2. [0,1,0]
  3. [0,0,1]

我希望这种表示是直观的。

对于您目前的情况,依赖性1)和2)没问题,因为0中有k,而1 / {{中有i 1}},这意味着该迭代不依赖于k的先前迭代来完成这两个相关性。

问题是由于3)。由于j中有一个1,因此每个迭代都取决于它的上一个迭代。如果我们能够以某种方式在k / >0中输入数字i,我们一定会成功。循环歪斜变换使我们可以完全相同。

3D示例有点难以理解。因此,让我们看一下使用ji的2D示例。

假设-j取决于R[i][j]R[i-1][j]。我们有同样的问题。

如果我们必须在图片中表示它,它看起来像这样-

R[i][j-1]

在此图中,每个点代表一个迭代. <- . <- . | | v v . <- . <- . | | v v . . . ,并且从每个点开始的箭头指向其依赖的迭代。显而易见,为什么我们不能在这里并行化最内部的循环。

但是假设我们的偏斜为-

(i,j)

如果您绘制与上图中相同的箭头(我无法在ASCII艺术作品中绘制对角箭头)。

您将看到所有箭头都指向下方,即它们至少向下迭代,这意味着您可以并行化水平循环。

现在说您的新循环尺寸为 . /| / | . . /| /| / | / | . . . /| / | . . . (外循环)和y(内循环),

您的原始变量xi将是

jj = 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一对一映射

您将看到RR_dash[y-1][x-1]都将在y的某些先前迭代中进行计算。因此,您可以完全并行化x循环。

此处应用的转换为

R_dash[y-1][x]

您可以类似地针对3个维度进行计算。

要进一步了解仿射变换如何工作以及如何将其用于引入并行性,请参见lecture notes