openmp 从相邻元素更新矩阵(并行预处理器)

计算科学 并行计算 线性求解器
2021-11-26 00:36:24

模板代码数据依赖性问题...

如何使用openmp并行化?我查看了 openmp 手册,我想出了如何使用 DO ORDERED 来获得与串行版本相同的结果,但是当使用 openmp 运行时,它会慢 x200 倍左右。

这是来自互联网的示例代码,它从相邻单元格进行更新,只是为了演示我所追求的问题。我只是在寻找正确方法的概念性描述,如果您愿意,还可以提供示例,但您不必花时间为这个特定案例实施。

  // Computation
  for ( i=1 ; i < N-1 ; i++ ) {
     for ( j=1 ; j < N-1 ; j++ ) {
        prev(i,j) = ((prev(i-1,j-1)+foo*prev(i-1,j)+bar*prev(i-1,j+1)+prev(i,j-1)+prev(i,j)+prev(i,j+1)+prev(i+1,j-1)+prev(i+1,j)+prev(i+1,j+1)));
     }
  }

您还可以在此处查看另一个类似问题的示例,第 6 页。


这里的另一个例子(在 C 中):

inline void NavierCalc::PSOR(int i,int j,int k,NS_REAL omg1,NS_REAL lomg) {

// Relaxiertes Gaus-Seidel-Verfahren
//
IFFLUID(flag[i][j][k]) {
SetPLocalBorder(P,i,j,k);
P[i][j][k]=omg1*P[i][j][k] - lomg/(S.ddPstar[0][3][i]+
     S.ddPstar[1][4][j]+S.ddPstar[2][5][k]) *  
  (S.ddPstar[0][6][i]*P[i+1][j][k] + S.ddPstar[0][0][i]*P[i-1][j][k] + 
   S.ddPstar[1][7][j]*P[i][j+1][k] + S.ddPstar[1][0][j]*P[i][j-1][k] +
   S.ddPstar[2][8][k]*P[i][j][k+1] + S.ddPstar[2][0][k]*P[i][j][k-1] 
   - RHS[i][j][k]);
}
}

一个相关的例子

这个页面,除了在我的情况下它不是 PX,它是左侧的 X,所以它依赖于自身,并且循环被分成两个(一个从左到右走并使用 SOMETHING-1 索引在右侧,而另一个从右到左走并在右侧使用 SOMETHING+1 索引):

 do j=2,size(X,dim=2)-1
 do i=2,size(X,dim=1)-1
    PX(i,j,bid) = precondNE    (i,j,bid)*X(i+1,j+1,bid) + &
                  precondNW    (i,j,bid)*X(i-1,j+1,bid) + &
                  precondSE    (i,j,bid)*X(i+1,j-1,bid) + &
                  precondSW    (i,j,bid)*X(i-1,j-1,bid) + &
                  precondNorth (i,j,bid)*X(i  ,j+1,bid) + &
                  precondSouth (i,j,bid)*X(i  ,j-1,bid) + &
                  precondEast  (i,j,bid)*X(i+1,j  ,bid) + &
                  precondWest  (i,j,bid)*X(i-1,j  ,bid) + &
                  precondCenter(i,j,bid)*X(i  ,j  ,bid)
 end do
 end do

另一个相关的例子

此页面- SSOR 预处理器:

!   Execute SSOR preconditioner.
    do j= 1,n-1
      do i = 1,n-1
        rhat(i,j) = w*(r(i,j)-aw(i,j)*rhat(i-1,j)   &
                     -as(i,j)*rhat(i,j-1))/ac(i,j)
      end do
    end do
    do j= 1,n-1
      do i = 1,n-1
        rhat(i,j) =  ((2.-w)/w)*ac(i,j)*rhat(i,j)
      end do
    end do
    do j= n-1,1,-1
      do i = n-1,1,-1
        rhat(i,j) = w*(rhat(i,j)-ae(i,j)*rhat(i+1,j)  &
                      -an(i,j)*rhat(i,j+1))/ac(i,j)
      end do
    end do

此演示文稿通过着色概述了概念或重新排序可能需要更多的例子来充分理解这个概念......

2个回答

由于您是从左到右、从上到下遍历网格,因此大多数步骤将取决于直接在上一步中计算的结果。如果你想保持顺序,这给并行性留下了很小的空间。您能做的最好的事情是让每个线程执行您的矩阵的一行,即仅在 上并行化i,并以某种方式尝试防止线程相互超越。

一般来说,同步点,即一个线程等待另一个线程的并行代码部分,总是会对并行性能产生负面影响,应该避免。

因此,您可能想问自己,您的并行代码准确复制串行结果有多重要?这种模板更新通常用作Gauss-Seidel 迭代或任何其他迭代方法的一部分,它应该收敛到正确的结果,并且通常不依赖于更新的确切顺序。

我认为您忽略了并行化代码的一个要点,那就是数据依赖性问题。你给出的所有例子都是这种形式

for i=1..N
   for j=1..N
      A(i,j) = foo*A(i-1,j-1) + foo*A(i-1,j+1) + foo*A(i+1,j+1) + ...

这不会并行化,因为更新 A 取决于其他循环迭代。处理依赖关系的一种方法是使用临时数组。例如:

#pragma omp parallel
   #pragma omp for
      for i=1..N
         for j=1..N
            temp(i,j) = foo*A(i-1,j-1) + foo*A(i-1,j+1) + foo*A(i+1,j+1) + ...

   #pragma omp for
      for i=1..N
         for j=1..N
            A(i,j) = temp(i,j)

除了重构你的算法之外,我还建议你在编译器中使用某种报告工具来查找正在向量化的内容。英特尔的编译器使用 -vec-report,我相信 GNU 有 -opt-report(仔细检查)。