我目前正在尝试使用一个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。
编辑:添加了可以运行的简化代码,尽管此代码有效
在代码中有两个循环,在并行和一个序列上,它们应该给出相同的结果,res
和res2
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)
不幸的是这段代码有效,所以我无法复制错误。 res2
和res
是按顺序和并行计算的。在我的真实程序中,我将所有值都放到1d0
以获得常量fe
。如果我在write(*,*) fe
之后添加calcFe
我看到值是正确的,那么计算的fe是正确的。然后,我将这些值添加到res2
,并将其与序列res
进行比较。然后它们的差异很大,因此没有数字舍入误差。如果我只是在主程序中声明fe=2304
,即使fe
在使用写入时已经是2304,我也会得到正确的答案。
在我的真实程序中,所有子程序都在不同的模块中,我是否需要特别小心呢?
同样在其中一个模块中使用了一些全局变量,它们是只读的,但由于它们未在子例程中声明,因此它们不会自动变为私有?这应该没有问题,因为我将所有变量用于将fe
计算为常量,全局变量不直接用于计算fe
答案 0 :(得分:0)
解决了它,当我将-openmp
添加到我的模块的makefile时,它开始工作。显然,模块需要使用-openmp
进行编译,而不仅仅是主文件。