不确定openmp循环中应该是SHARED还是PRIVATE

Gri*_*iff 11 parallel-processing fortran loops openmp

我有一个更新矩阵A的循环,我想让它变为openmp但我不确定应该共享哪些变量和私有.我本以为只有ii和jj会起作用,但事实并非如此.我想我也需要一个!$ OMP ATOMIC UPDATE ...

循环只计算N和N-1粒子之间的距离并更新矩阵A.

            !$OMP PARALLEL DO PRIVATE(ii,jj)
            do ii=1,N-1
                    do jj=ii+1,N
                            distance_vector=X(ii,:)-X(jj,:)
                            distance2=sum(distance_vector*distance_vector)
                            distance=DSQRT(distance2)
                            coff=distance*distance*distance
                            PE=PE-M(II)*M(JJ)/distance
                            A(jj,:)=A(jj,:)+(M(ii)/coff)*(distance_vector)
                            A(ii,:)=A(ii,:)-(M(jj)/coff)*(distance_vector)
                    end do
            end do
            !$OMP END PARALLEL DO
Run Code Online (Sandbox Code Playgroud)

Hri*_*iev 24

OpenMP的黄金法则是在外部作用域中定义的所有变量(带有一些排除)在默认情况下在并行区域中共享.因为在2008年之前的Fortran中没有本地范围(即BLOCK ... END BLOCK在早期版本中没有),所有变量(除了threadprivate一些)都是共享的,这对我来说非常自然(与Ian Bush不同,我不是很喜欢使用default(none),然后重新确定各种复杂科学代码中所有100多个局部变量的可见性.

以下是如何确定每个变量的共享类:

  • N - 共享,因为它应该在所有线程中都相同,并且它们只读取其值.
  • ii- 它是循环的计数器,受工作共享指令的约束,因此它的共享类是预定的private.在PRIVATE子句中明确声明它并没有什么坏处,但这并不是必需的.
  • jj- 循环的循环计数器,它不受工作共享指令的约束,因此jj应该是private.
  • X - 共享,因为所有线程都引用并且只读取它.
  • distance_vector- 显然应该是private每个线程在不同的粒子对上工作.
  • distance,distance2coff-同上.
  • M- 出于同样的原因应该分享X.
  • PE- 充当累加器变量(我猜这是系统的潜在能量)并且应该是减少操作的主题,即应该放在一个REDUCTION(+:....)子句中.
  • A - 这个很棘手.它可以是共享和A(jj,:)使用同步构造进行保护的更新,也可以使用简化(OpenMP允许减少Fortran中的数组变量,与C/C++不同).A(ii,:)永远不会被多个线程修改,因此不需要特殊处理.

随着减少A到位,每个线程都会获得它的私有副本,A这可能是一个内存耗尽,虽然我怀疑你会使用这个直接的O(N 2)模拟代码来计算具有大量粒子的系统.还原实施还存在一定的开销.在这种情况下,您只需要添加AREDUCTION(+:...)子句列表中.

使用同步结构,您有两个选择.您可以使用ATOMIC构造或CRITICAL构造.由于ATOMIC仅适用于标量上下文,您必须"unvectorise"赋值循环并分别应用于ATOMIC每个语句,例如:

!$OMP ATOMIC UPDATE
A(jj,1)=A(jj,1)+(M(ii)/coff)*(distance_vector(1))
!$OMP ATOMIC UPDATE
A(jj,2)=A(jj,2)+(M(ii)/coff)*(distance_vector(2))
!$OMP ATOMIC UPDATE
A(jj,3)=A(jj,3)+(M(ii)/coff)*(distance_vector(3))
Run Code Online (Sandbox Code Playgroud)

您也可以将其重写为循环 - 不要忘记声明循环计数器private.

因为CRITICAL没有必要对循环进行非操作:

!$OMP CRITICAL (forceloop)
A(jj,:)=A(jj,:)+(M(ii)/coff)*(distance_vector)
!$OMP END CRITICAL (forceloop)
Run Code Online (Sandbox Code Playgroud)

命名关键区域是可选的,在这种特定情况下有点不必要但通常它允许分离不相关的关键区域.

哪个更快?展开ATOMICCRITICAL?这取决于很多事情.通常CRITICAL速度较慢,因为它通常涉及对OpenMP运行时的函数调用,而至少在x86上的原子增量是使用锁定的加法指令实现的.正如他们常说的那样,YMMV.

总结一下,循环的工作版本应该是这样的:

!$OMP PARALLEL DO PRIVATE(jj,kk,distance_vector,distance2,distance,coff) &
!$OMP& REDUCTION(+:PE)
do ii=1,N-1
   do jj=ii+1,N
      distance_vector=X(ii,:)-X(jj,:)
      distance2=sum(distance_vector*distance_vector)
      distance=DSQRT(distance2)
      coff=distance*distance*distance
      PE=PE-M(II)*M(JJ)/distance
      do kk=1,3
         !$OMP ATOMIC UPDATE
         A(jj,kk)=A(jj,kk)+(M(ii)/coff)*(distance_vector(kk))
      end do
      A(ii,:)=A(ii,:)-(M(jj)/coff)*(distance_vector)
   end do
end do
!$OMP END PARALLEL DO
Run Code Online (Sandbox Code Playgroud)

我假设你的系统是三维的.


尽管如此,我还是Ian Bush,你需要重新思考位置和加速度矩阵是如何在记忆中布局的.适当的高速缓存使用可以增强您的代码并且还允许某些操作,例如X(:,ii)-X(:,jj)被矢量化,即使用矢量SIMD指令实现.