openmp fortran减少并且严重不适用于数组

时间:2015-10-13 14:52:04

标签: arrays fortran openmp

我目前正在尝试使用一个fortran FE(有限元)代码来使用openmp。我想要并行工作的所有元素ie都有一个循环。以下是代码的简化部分

     !$omp parallel do default(none) shared(nelm,A,res,enod) private(ie,Fe,B,edof)     
    do ie=1,nelm 
       call calcB(B,A(:,ie))
       call calcFe(Fe,B)
       write(*,*) Fe !writes Fe=40d0, this is correct

        call getEdof(edof,enod(:,ie))
        !$OMP CRITICAL      
        res(edof)=res(edof)+fe
        !$OMP END CRITICAL 
        enddo 
!$omp end parallel do

代码的目的是计算一个力Fe,然后将其添加到res edof。使用calcFe计算力,并且计算的力是正确的,但在循环后得到的res不正确。

如果我用calcFe替换Fe=40d0,则将其添加到res,结果在循环后正确

     !$omp parallel do default(none) shared(nelm,A,res,enod) private(ie,Fe,B,edof)


    do ie=1,nelm 
       call calcB(B,A(:,ie))

       Fe=40d0

        call getEdof(edof,enod(:,ie))
        !$OMP CRITICAL      
        res(edof)=res(edof)+fe
        !$OMP END CRITICAL 
        enddo 
!$omp end parallel do

导致此错误的原因是什么?在这两种情况下,Fe=40d0都声明为private,但只有其中一个会给出正确的结果。我没有使用!$ CRITICAL,而是使用reduction,但它会出现同样的错误。在程序中,还使用了几个大的稀疏矩阵,但是在循环期间是被动的/不使用的。我的主管之前遇到过使用openmp和稀疏矩阵的问题,并怀疑他们使用相同的内存。如果错误不明显,最好使用哪个调试器?我是fortran,openmp和编程的新手 我使用ifort进行编译,我的操作系统是ubuntu。

编辑:添加了可以运行的简化代码,尽管此代码有效 在代码中有两个循环,在并行和一个序列上,它们应该给出相同的结果,resres2

program main

use omp_lib

implicit none

integer :: ie, nelm,enod(4,50*50),edof(12),i,j,k
double precision ::B(12,8),fe(12),A(12,12,2500),res(2601*3),res2(2601*3),finish,start

  !creates  enod
  i=1
  do j=1,50
    ie=j
    do k=1,50
       nelm=k
       enod(:,i)=(/ 51*(nelm-1)+1+ie-1, 51*(nelm-1)+1+ie, 51*(nelm)+1+ie-1, 51*(nelm)+1+ie /)
        i=i+1
    end do
  end do

   A=1d0
   res2=0d0
   nelm=2500   
   start=omp_get_wtime()          
   !$omp parallel do default(none) shared(nelm,A,enod) private(ie,fe,edof,B) reduction(+:res2)
      do ie=1,nelm
            call calcB(B,A(:,:,ie))
          call calcFe(fe,B) !the calculated fe is always 2304
          !can write fe=2304 to get correct result with real code
          call getEdof(edof,enod(:,ie))
          res2(edof)=res2(edof)+fe
      end do
   !$omp end parallel do
   finish=omp_get_wtime()
    write(*,*) 'time: ', finish-start

   res=0d0
   nelm=2500             
   start=omp_get_wtime()
      do ie=1,nelm
            call calcB(B,A(:,:,ie))
          call calcFe(fe,B)
          call getEdof(edof,enod(:,ie))
          res(edof)=res(edof)+fe
      end do
        finish=omp_get_wtime()
    write(*,*) 'time: ', finish-start
    write(*,*) 'difference: ',sum(res2-res) 
    write(*,*) sum(res2)

stop

end program main

subroutine calcB(B,A)
double precision ::B(12,8),A(12,12),C(12)
integer          ::gp
  C=1d0
  do gp=1,8
    B(:,gp)=matmul(A,C)
  end do
end subroutine calcB


subroutine calcFe(fe,B)
double precision  ::fe(12),B(12,8),D(12,12)
integer           ::gp
  fe=0d0
  D=2d0

  do gp=1,8
    fe=fe+matmul(D,B(:,gp))
  end do


end subroutine calcFe


subroutine getEdof(edof,enod)
    implicit none
    integer,intent(in) :: enod(4)
    integer,intent(out):: edof(12)
    edof=0
    edof(1:3)  =(/ enod(1)*3-2, enod(1)*3-1, enod(1)*3 /)
    edof(4:6)  =(/ enod(2)*3-2, enod(2)*3-1, enod(2)*3 /)
    edof(7:9)  =(/ enod(3)*3-2, enod(3)*3-1, enod(3)*3 /)
    edof(10:12)=(/ enod(4)*3-2, enod(4)*3-1, enod(4)*3 /)

end subroutine getedof

和make文件

    FF = ifort -O3 -openmp
OBJ1  = main.f90
ls: $(FORT_OBJS)
    $(FF) -o exec $(OBJ1)

不幸的是这段代码有效,所以我无法复制错误。 res2res是按顺序和并行计算的。在我的真实程序中,我将所有值都放到1d0以获得常量fe。如果我在write(*,*) fe之后添加calcFe我看到值是正确的,那么计算的fe是正确的。然后,我将这些值添加到res2,并将其与序列res进行比较。然后它们的差异很大,因此没有数字舍入误差。如果我只是在主程序中声明fe=2304,即使fe在使用写入时已经是2304,我也会得到正确的答案。

在我的真实程序中,所有子程序都在不同的模块中,我是否需要特别小心呢? 同样在其中一个模块中使用了一些全局变量,它们是只读的,但由于它们未在子例程中声明,因此它们不会自动变为私有?这应该没有问题,因为我将所有变量用于将fe计算为常量,全局变量不直接用于计算fe

1 个答案:

答案 0 :(得分:0)

解决了它,当我将-openmp添加到我的模块的makefile时,它开始工作。显然,模块需要使用-openmp进行编译,而不仅仅是主文件。