Loops 不确定什么应该在openmp循环中共享或私有

Loops 不确定什么应该在openmp循环中共享或私有,loops,parallel-processing,fortran,openmp,Loops,Parallel Processing,Fortran,Openmp,我有一个更新矩阵a的循环,我想让它成为openmp,但我不确定哪些变量应该共享和私有。我本以为只有ii和jj可以工作,但事实并非如此。我想我需要一份工作$OMP原子更新也在某处 循环只计算N和N-1粒子之间的距离,并更新矩阵a !$OMP PARALLEL DO PRIVATE(ii,jj) do ii=1,N-1 do jj=ii+1,N distan

我有一个更新矩阵a的循环,我想让它成为openmp,但我不确定哪些变量应该共享和私有。我本以为只有ii和jj可以工作,但事实并非如此。我想我需要一份工作$OMP原子更新也在某处

循环只计算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

如前所述,您需要进行一些同步以避免出现竞速情况。考虑2线程的情况。假设线程0以ii=1开始,因此考虑jj=2,3,4。。。。线程1从ii=2开始,因此考虑jj=3,4,5,6。因此,正如所写的,线程0可能正在考虑ii=1,jj=3,而线程1可能正在同时考虑ii=2,jj=3。这显然会导致生产线出现问题

                        A(jj,:)=A(jj,:)+(M(ii)/coff)*(distance_vector) 
因为两个线程具有相同的jj值。所以,是的,您确实需要将更新同步到以避免竞争,尽管我必须承认,我的好方法对我来说并不是很明显。如果我有什么事,我会仔细考虑并编辑

不过,我还有3点意见:

1) 您的内存访问模式非常糟糕,我认为,纠正这一点将至少提供与任何openmp相同的速度,而且麻烦更少。在Fortran中,您希望以最快的速度降低第一个索引-这确保了内存访问在空间上是本地的,从而确保了内存层次结构的良好使用。考虑到这是在现代机器上获得良好性能的最重要的因素,您应该真正尝试正确实现这一点。因此,如果您可以安排数组,以便将上面的内容写成

        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
请注意这是如何在第一个索引中向下移动的,而不是在第二个索引中

2) 如果您确实使用openmp,我强烈建议您使用默认(无),它有助于避免讨厌的bug。如果你是我的学生,你会因为不这样做而失去很多分数


3) Dsqrt是古老的——在现代Fortran语言中(即1967年以后的任何语言),除了少数模糊的情况外,sqrt已经足够好了,并且更加灵活了。OpenMP的黄金法则是,在外部范围内定义的所有变量(包括一些排除项)默认在并行区域中共享。因为在2008年之前的Fortran中没有局部作用域(即早期版本中没有
块…END块
),所以所有变量(除了
threadprivate
变量)都是共享的,这对我来说是很自然的(与Ian Bush不同,我不太喜欢使用
默认值(无)
,然后重新说明各种复杂科学代码中所有100多个局部变量的可见性)

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

  • N
    -共享,因为它在所有线程中都应该相同,并且它们只读取它的值
  • ii
    -它是循环的计数器,受工作共享指令的约束,因此它的共享类被预先确定为
    私有
    。在
    PRIVATE
    子句中显式声明它并没有什么坏处,但这并不是真正必要的
  • jj
    -循环的循环计数器,它不受工作共享指令的约束,因此
    jj
    应该是
    专用的
  • X
    -共享,因为所有线程都引用并仅从中读取
  • distance\u vector
    -显然应该是
    private
    ,因为每个线程都作用于不同的粒子对
  • distance
    distance 2
    coff
    -同上
  • M
    -共享的原因应与
    X
    相同
  • PE
    -充当累加器变量(我猜这是系统的势能),应该是还原操作的主题,即应该放在
    还原(+:…)
    子句中
  • A
    -这个问题很棘手。它可以通过同步结构共享和更新到
    A(jj,:)
    ,也可以使用reduce(OpenMP允许对Fortran中的数组变量进行reduce,这与C/C++不同)
    A(ii,:)
    从不被多个线程修改,因此不需要特殊处理
随着
A
的缩减到位,每个线程将获得
A
的私有副本,这可能是一个内存占用,尽管我怀疑您是否会使用此直接O(N2)模拟代码来计算具有大量粒子的系统。还存在与缩减实现相关的特定开销。在这种情况下,只需将
A
添加到
reduce(+:…)
子句的列表中

对于同步构造,您有两种选择。您可以使用
原子
构造或
关键
构造。由于
ATOMIC
仅适用于标量上下文,因此您必须“取消”赋值循环,并将
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))
您也可以将其重写为循环-不要忘记声明循环计数器
private

对于
临界值
,无需取消回路的反射:

!$OMP CRITICAL (forceloop)
A(jj,:)=A(jj,:)+(M(ii)/coff)*(distance_vector)
!$OMP END CRITICAL (forceloop)
命名关键区域是可选的,在这种特殊情况下有点不必要,但通常它允许分离不相关的关键区域

哪个更快?使用
原子
关键
展开?这取决于很多事情。通常
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
我假设你的系统是三维的


说到这里,我支持伊恩·布什,你需要重新思考位置和加速度矩阵在内存中的布局。正确使用缓存可以提高代码的性能,还可以允许某些操作,例如。