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

时间:2013-01-16 14:41:51

标签: loops parallel-processing fortran 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

2 个答案:

答案 0 :(得分:24)

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

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

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

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

使用同步结构,您有两个选择。您可以使用REDUCTION(+:...)构造或ATOMIC构造。由于CRITICAL仅适用于标量上下文,因此您必须“取消”分配循环,并将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时,无需对循环进行取消操作:

CRITICAL

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

哪个更快?已展开!$OMP CRITICAL (forceloop) A(jj,:)=A(jj,:)+(M(ii)/coff)*(distance_vector) !$OMP END CRITICAL (forceloop) ATOMIC?这取决于很多事情。通常CRITICAL速度较慢,因为它通常涉及对OpenMP运行时的函数调用,而至少在x86上的原子增量是使用锁定的加法指令实现的。正如他们常说的,YMMV。

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

CRITICAL

我认为你的系统是三维的。


说完这一切,我第二个伊恩布什,你需要重新思考位置和加速矩阵是如何在记忆中布局的。适当的缓存使用可以增强您的代码,并且还允许某些操作,例如,要!$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 进行矢量化,即使用矢量SIMD指令实现。

答案 1 :(得分:3)

如上所述,您需要一些同步以避免竞争条件。考虑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值。所以,是的,你确实需要将更新同步到A以避免比赛,但我必须承认我的好方法对我来说并不是很明显。如果发生某些事情,我会考虑并编辑。

但是我还有其他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我强烈建议你使用默认(无),它有助于避免讨厌的错误。如果你是我的学生之一,你就会因为不这样做而失去大量的分数!

3)Dsqrt很古老 - 在现代Fortran中(即1967年以后的任何东西)除了少数几个不起眼的案例外,sqrt足够好,而且更灵活