并行运行代码时出现错误的结果

时间:2015-06-05 14:32:01

标签: fortran openmp gfortran hpc

当我使用OpenMP运行并行程序时,gfortran编译器给出了错误的答案。同时,ifort会给出准确的结果。

这是完整的可编辑代码。

!_______________________________________________________________ !
!____________MODULE SECTION_____________________________________ !

  MODULE MATRIC
    IMPLICIT NONE
    INTEGER , PARAMETER :: NG = 40  
    DOUBLE PRECISION,SAVE :: Z , PA , PB ,CMU 
    DOUBLE PRECISION , PARAMETER :: PI=2.0D0*ACOS(0.0D0) , &
             FPI=4.0D0*PI , SQFPI = SQRT(FPI), DLAM=1.0D0
    DOUBLE PRECISION , DIMENSION(450) :: DEL1,  DEL2, X,  R ,  SNLO 
    DOUBLE PRECISION :: XG(60) , WG(60) 
  END MODULE MATRIC
!_________________________________________________________________________!
!                  MODULE SECTION 
!__________________________________________________________________________!

  MODULE POTDATA
    IMPLICIT NONE
    INTEGER                            :: IA , IB , ID       
    DOUBLE PRECISION                   :: RA , RB , R1s(450)     
  END MODULE POTDATA
!__________________________________________________________________________!



!______________________________________________________________________!

  program check
    use matric
    use potdata
    implicit double precision(a-h,o-z)

    pa   = 0.72D0  ;  pb   =  0.19D0  
    mesh = 441     ;  noint=  40      ;  z   =  2.0d0    
    CALL GAULEG(-1.d0,1.d0)

    NB = MESH/NOINT
    I = 1
    X(I) = 0.0D+00
    DELTAX = 0.0025D+00*40.0D+00/DBLE(NOINT)
    DO  J=1,NB
      IMK = (J-1)*NOINT + 1
      DO K=1,NOINT
        AK=K
        I=I+1
        X(I)=X(IMK)+AK*DELTAX
      END DO
      DELTAX=2.0D+00*DELTAX
    END DO

    CMU=(9.0D00*PI*PI/(128.0D00*Z))**THIRD

    R(1)=0.0D+00 ;  SNLO(1) = 0.D00
    DO  I=2,MESH
      R(I)=CMU*X(I)
      SNLO(I) = R(I)*dexp(-Z*R(I))
      R1S(I) = SNLO(I)/(SQFPI*R(I))
    END DO

    call EFFPOT(MESH,NOINT)

  end program check


  subroutine EFFPOT(MESH,NOINT)
    USE OMP_LIB
    USE MATRIC  
    USE POTDATA 
    implicit none 
    integer, intent(in) :: MESH, NOINT 
    double precision::anorm(450)
    double precision, external :: funct
    double precision :: asum, fac, cnorm

!$omp parallel do default(none) private(del1,ia,asum,ib,ra,rb,fac) &
!$omp shared(id,mesh,r,anorm,NOINT,del2,R1s)
    do  ia = 2,mesh
      ra = r(ia)
      if(R1s(ia).lt.1.D-7.and.R1s(ia).ge.1.D-8)id = ia
      do ib = 2,mesh
         rb = r(ib)
         call QGAUSS(funct,-1.d0,1.d0,fac)
         del1(ib) = rb**2*fac*R1s(ib)**2
      end do
      CALL NCDF(del1,ASUM,r(2),mesh,NOINT)
      anorm(ia) = 2.0d0*pi*asum
      del2(ia)  = 2.0d0*pi*asum*(ra*R1s(ia))**2
    end do
!$omp end parallel do

    CALL NCDF(del2,ASUM,r(2),mesh,NOINT)
    cnorm = 1.0/dsqrt(4.*pi*ASUM)
    write(6,*)'cnorm =',cnorm

    return 
  end


  double precision function funct(x)

    USE POTDATA , ONLY : RA , RB 
    USE MATRIC  , ONLY : PA , PB  , DLAM

    implicit none
    double precision, intent(in) :: x
    double precision             :: f1, f2, ramrb

    ramrb = dsqrt(ra**2+rb**2-2.d0*ra*rb*x)
    f1 = dcosh(pa*ra)+dcosh(pa*rb)
    f2  = 1.d0+0.5*dlam*ramrb*dexp(-pb*ramrb)
    funct = (f1*f2)**2
    return
  end


  SUBROUTINE QGAUSS(func,aa,bb,ss)
    USE OMP_LIB
    USE MATRIC , ONLY : XG ,WG , NG 
    IMPLICIT DOUBLE PRECISION(A-H,O-Z)
    external func
    xm = 0.5d0*(bb+aa)
    xl = 0.5d0*(bb-aa)
    ss = 0.d0
    do  j=1,ng
      dx = xl*xg(j)
      ss = ss + wg(j)*(func(xm+dx)+func(xm-dx))
    end do
    ss = xl*ss/2.0
    return
  END


  SUBROUTINE GAULEG(x1,x2)

    USE MATRIC , ONLY : XG ,WG ,NG , PI

    IMPLICIT DOUBLE PRECISION(A-H,O-Z)
    eps = 1.d-14
    m = (ng+1)/2
    xm = 0.5D0*(x1+x2)
    xl = 0.5D0*(x2-x1)

    do i=1,m
      z = dcos(pi*(dfloat(i)-0.25d0)/(dfloat(ng)+0.5d0))
1     continue
      p1 = 1.d0
      p2 = 0.d0

      do j=1,ng
        p3 = p2
        p2 = p1
        p1 = ((2.d0*dfloat(j)-1.d0)*z*p2  &
          - (dfloat(j)-1.d0)*p3)/dfloat(j)
      end do

      pp = dfloat(ng)*(z*p1-p2)/(z*z-1.d0)
      z1 = z
      z = z1 - p1/pp
      if (dabs(z-z1).gt.eps) go to 1
      xg(i) = xm - xl*z
      xg(ng+1-i) = xm + xl*z
      wg(i) = 2.d0*xl/((1.d0-z*z)*pp*pp)
      wg(ng+1-i) = wg(i)                          
    end do
    return
  end


  SUBROUTINE NCDF(F,ASUM,H,KKK,NOINT)
    IMPLICIT DOUBLE PRECISION (A-H,O-Z)
    DIMENSION F(450)
    NBLOCK = (KKK-2)/NOINT + 1
    C2HO45 = 2.0D+00*H/45.0D+00      
    ASUM = 0.0D+00

    DO  J=1,NBLOCK
      ISTAR = NOINT*(J-1)+5
      IEND = NOINT*J + 1
      IEND = MIN0(KKK,IEND)
      DO  I=ISTAR,IEND,4
          ASUM = ASUM + C2HO45*(7.0D+00*(F(I-4)+F(I))  &
                +32.0D+00*(F(I-3)+F(I-1)) + 12.0D+00*F(I-2))
      END DO
      IF(IEND.EQ.KKK) GO TO 4
      C2HO45 = 2.0D+00*C2HO45
4   END DO

    RETURN
  END

非常感谢@Vladimir,他对我的问题感兴趣。最后,通过从模块potdata中删除ra和rb并将函数定义为funct(x,ra,rb),然后从循环中删除ra和rb,我得到了正确的答案。因为我正在编写ra,rb然后在上面的代码中读取它们的值,所以循环具有流依赖性。现在我从两个编译器(也就是8.7933767516)并行地顺序获得了精确的结果。确切的方法是这个

subroutine EFFPOT(MESH,NOINT)
    USE OMP_LIB
    USE MATRIC  
    USE POTDATA 
  implicit none 
  integer, intent(in) :: MESH, NOINT 
  double precision::anorm(450)
  double precision, external :: funct
  double precision :: asum, fac, cnorm
 !$omp parallel do default(none) private(del1,ia,asum,ib,fac) &
 !$omp shared(id,mesh,r,anorm,NOINT,del2,R1s)

  do  ia = 2,mesh
      if(R1s(ia).lt.1.D-7.and.R1s(ia).ge.1.D-8)id = ia
      do ib = 2,mesh
         call QGAUSS(funct,-1.d0,1.d0,fac,r(ia),r(ib))
         del1(ib) = r(ib)**2*fac*R1s(ib)**2
      end do
      CALL NCDF(del1,ASUM,r(2),mesh,NOINT)
      anorm(ia) = 2.0d0*pi*asum
      del2(ia)  = 2.0d0*pi*asum*(r(ia)*R1s(ia))**2
  end do

 !$omp end parallel do
  CALL NCDF(del2,ASUM,r(2),mesh,NOINT)
  cnorm = 1.0/dsqrt(4.*pi*ASUM)
  write(6,*)'cnorm =',cnorm

  return 
  end


      double precision function funct(x,ra,rb)
      USE MATRIC  , ONLY : PA , PB  , DLAM

      implicit none
      double precision, intent(in) :: x, ra, rb
      double precision             :: f1, f2, ramrb

      ramrb = dsqrt(ra**2+rb**2-2.d0*ra*rb*x)
      f1 = dcosh(pa*ra)+dcosh(pa*rb)
      f2  = 1.d0+0.5*dlam*ramrb*dexp(-pb*ramrb)
      funct = (f1*f2)**2
  return
  end
  SUBROUTINE QGAUSS(func,aa,bb,ss,ra,rb)
     USE OMP_LIB
     USE MATRIC , ONLY : XG ,WG , NG 
     IMPLICIT DOUBLE PRECISION(A-H,O-Z)
     external func
     xm = 0.5d0*(bb+aa)
     xl = 0.5d0*(bb-aa)
     ss = 0.d0
   do  j=1,ng
     dx = xl*xg(j)
     ss = ss + wg(j)*(func(xm+dx,ra,rb)+func(xm-dx,ra,rb))
   end do
   ss = xl*ss/2.0
   return
  END

2 个答案:

答案 0 :(得分:4)

问题的原因是OpenMP标准没有指定在区域中但在构造外部访问private列表项时会发生什么。有关同一问题的简短版本,请参阅示例private.2f(在OpenMP标准补充的第135页上找到)。

具体而言,模块变量rarbprivate内的OpenMP并行区域中声明为EFFPOT,并且也可以从funct访问。 funct位于并行区域的动态范围内,但(词汇上)位于其外部,因此未指定ra引用的rbfunct是否为原始模块变量或它们的私有副本(大多数编译器都会使用原始变量)。

您已经找到了其中一种解决方案。另一个是声明rarb threadprivate,因为它们仅用于将数据从EFFPOT传递到funct

MODULE POTDATA
  IMPLICIT NONE
  INTEGER                            :: IA , IB , ID       
  DOUBLE PRECISION                   :: RA , RB , R1s(450)
  !$OMP THREADPRIVATE(RA,RB)
END MODULE POTDATA

然后,您还应该从ra中的并行区域的rb子句列表中删除privateEFFPOT

在某些平台上,例如OS X,使用threadprivate和GCC(即gfortran)可能比实际传递两个变量作为参数要慢,因为模拟的TLS​​。

请注意,这个语义错误实际上很难被发现,许多OpenMP工具实际上都没有发现它。

答案 1 :(得分:1)

首先,在没有看到实际代码的情况下很难说出具体的内容。但是,我确实对你的情况和你得出的结论有一些评论。

当使用“ifort”编译时,您的程序在并行和顺序执行中运行良好这一事实并不意味着您的程序是正确的。由于导致程序提供错误答案的编译器错误非常罕见,但另一方面,手动并行编程非常容易出错,我们应该假设您对代码进行并行化的方式存在问题。我们可能正在讨论race condition

不,你遇到的问题并不罕见。当您遇到竞争条件时,通常会发生顺序执行在任何地方都有效,并且您的并行执行在某些环境中有效,而在其他环境中无效。甚至常见的是,每次调用代码时,代码都会给出不同的答案(不仅取决于编译器,还取决于可能随时间变化的许多其他因素)。

我建议您应该做的是获得并行调试器,例如TotalView,它将帮助您跟踪各种线程及其状态。尝试找到一个可靠失败的简单测试环境(尽可能少的线程)。