我有一个更新矩阵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
答案 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
,因为每个主题都适用于不同的粒子对。distance
,distance2
和coff
- 同上。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足够好,而且更灵活