使用 FORTRAN+OpenMP 的 do 循环的奇怪结果
Weird result for a do loop with FORTRAN+OpenMP
首先我编译了没有 -fopenmp
的代码,运行 代码,得到了一个串行结果,这是一个基准。
其次,我考虑使用 OpenMP 来加速我的代码。
有两个奇怪的结果:
1.The
获得的结果
!$OMP CRITICAL
p1=p1+1
!$OMP END CRITICAL
与串行结果有一点差异(1%)。我的代码不包含随机数,所以它一定是错误的。
2. 如果我用!$OMP ATOMIC
替换!$OMP CRITICAL
并删除!$OMP END CRITICAL
,这两者之间的结果是完全不同的。 p1=p1+1
这种情况下两者不是可以互相替代的吗?
我的想法:
1. 最常见的问题可能是使用线程不安全的子例程。但是我在下面的代码中找不到。
这是从我的代码中复制的一小部分。
注意:
- 除了 i,j,k,k1,k2,k3,distance 之外的变量被认为是 "SHARED"
- 下面的代码是我在整个代码中并行化的唯一部分。
(i,j,k,k1,k2,k3,distance)在并行循环后没有使用,所以不会考虑"PRIVATE"声明的私有变量的不确定性.
p1=0;
!$OMP PARALLEL PRIVATE(i,j,k,k1,k2,k3,distance)
!$OMP DO
do i=1,N_MESH;do j=1,N_MESH;do k=1,N_MESH;
!$OMP CRITICAL
p1=p1+1
!$OMP END CRITICAL
! - box-1. special treatment for doing specturm operations for FFT.
if (i.lt.(N_MESH/2+1))then
k1=i-1
elseif (i.eq.N_MESH/2+1) then
k1=0
else
k1=i-1-N_MESH
endif
if (j.lt.(N_MESH/2+1))then
k2=j-1
elseif (j.eq.N_MESH/2+1)then
k2=0
else
k2=j-1-N_MESH
endif
if (k.lt.(N_MESH/2+1))then
k3=k-1
elseif (k.eq.N_MESH/2+1)then
k3=0
else
k3=k-1-N_MESH
endif
! =============distance =====================
distance=(k1*k1)+(k2*k2)+(k3*k3);
! -----============put them into =======================
final_index(p1,l) = nint(dsqrt(distance));
if (((k1.eq.0).AND.(k2.eq.0)).AND.(k3.eq.0)) THEN
final(p1,l)=0d0
else
final(p1,l)=(abs(fu(i,j,k))**2+abs(fv(i,j,k))**2+abs(fw(i,j,k))**2)/2d0
endif
enddo;enddo;enddo
!$OMP END DO
!$OMP END PARALLEL
问题是 p1
是共享的,可能会在循环主体期间发生变化。
假设您有两个线程,p1 从零开始,并且它们同时开始。第一等级 0 到达临界区并递增 p1
到 1,而等级 1 等待临界区结束。一旦 rank 0 完成递增 p1
,它开始执行其余代码,但同时 rank 1 开始执行临界区,并递增 p1
。不能保证等级 0 会在 p1
变为 2 之前到达 final(p1,l) = ...
语句。如果发生这种情况,final(1,l)
将永远不会更新。所以存在竞争条件。
为避免此问题,我建议您根据i
、j
和k
手动计算p1
。这将使您可以将 p1
设为私有,为您节省关键部分,并消除此竞争条件。
p1 = k + N_MESH*(j-1+N_MESH*(i-1))
首先我编译了没有 -fopenmp
的代码,运行 代码,得到了一个串行结果,这是一个基准。
其次,我考虑使用 OpenMP 来加速我的代码。
有两个奇怪的结果: 1.The
获得的结果 !$OMP CRITICAL
p1=p1+1
!$OMP END CRITICAL
与串行结果有一点差异(1%)。我的代码不包含随机数,所以它一定是错误的。
2. 如果我用!$OMP ATOMIC
替换!$OMP CRITICAL
并删除!$OMP END CRITICAL
,这两者之间的结果是完全不同的。 p1=p1+1
这种情况下两者不是可以互相替代的吗?
我的想法: 1. 最常见的问题可能是使用线程不安全的子例程。但是我在下面的代码中找不到。
这是从我的代码中复制的一小部分。
注意:
- 除了 i,j,k,k1,k2,k3,distance 之外的变量被认为是 "SHARED"
- 下面的代码是我在整个代码中并行化的唯一部分。
(i,j,k,k1,k2,k3,distance)在并行循环后没有使用,所以不会考虑"PRIVATE"声明的私有变量的不确定性.
p1=0; !$OMP PARALLEL PRIVATE(i,j,k,k1,k2,k3,distance) !$OMP DO do i=1,N_MESH;do j=1,N_MESH;do k=1,N_MESH; !$OMP CRITICAL p1=p1+1 !$OMP END CRITICAL ! - box-1. special treatment for doing specturm operations for FFT. if (i.lt.(N_MESH/2+1))then k1=i-1 elseif (i.eq.N_MESH/2+1) then k1=0 else k1=i-1-N_MESH endif if (j.lt.(N_MESH/2+1))then k2=j-1 elseif (j.eq.N_MESH/2+1)then k2=0 else k2=j-1-N_MESH endif if (k.lt.(N_MESH/2+1))then k3=k-1 elseif (k.eq.N_MESH/2+1)then k3=0 else k3=k-1-N_MESH endif ! =============distance ===================== distance=(k1*k1)+(k2*k2)+(k3*k3); ! -----============put them into ======================= final_index(p1,l) = nint(dsqrt(distance)); if (((k1.eq.0).AND.(k2.eq.0)).AND.(k3.eq.0)) THEN final(p1,l)=0d0 else final(p1,l)=(abs(fu(i,j,k))**2+abs(fv(i,j,k))**2+abs(fw(i,j,k))**2)/2d0 endif enddo;enddo;enddo !$OMP END DO !$OMP END PARALLEL
问题是 p1
是共享的,可能会在循环主体期间发生变化。
假设您有两个线程,p1 从零开始,并且它们同时开始。第一等级 0 到达临界区并递增 p1
到 1,而等级 1 等待临界区结束。一旦 rank 0 完成递增 p1
,它开始执行其余代码,但同时 rank 1 开始执行临界区,并递增 p1
。不能保证等级 0 会在 p1
变为 2 之前到达 final(p1,l) = ...
语句。如果发生这种情况,final(1,l)
将永远不会更新。所以存在竞争条件。
为避免此问题,我建议您根据i
、j
和k
手动计算p1
。这将使您可以将 p1
设为私有,为您节省关键部分,并消除此竞争条件。
p1 = k + N_MESH*(j-1+N_MESH*(i-1))