【问题标题】:OpenMP slower than serial Fortran90 codeOpenMP 比串行 Fortran90 代码慢
【发布时间】:2017-06-20 13:10:57
【问题描述】:

在我的 Fortran90 代码中,我有一个带有多个嵌套循环的外循环。为了加快我的代码,我尝试在外循环上使用 OpenMP,但我有一个非常奇怪的问题:当我使用超过 1 个线程时,程序运行速度比使用带有 1 个线程的 OMP 慢,而 OMP 又比使用慢原始串行程序(就挂钟时间而言。我尝试使用 1、2、3 或 4 个线程)。然而,在所有情况下,我都得到了正确的结果。

我对我的代码进行了几次测试,最后我注意到问题出在一个子例程中,因为如果我注释了对该例程的调用,我的并行程序按预期工作,即线程数越大,越低挂钟时间。

现在,该例程接受输入 4 个向量“ks1”、“ks2”、“ket1”、“ket2”,并执行“ks1”和“ks2”之间的联合,获得“kstot”。然后它创建 2 个新向量,“ket1tot”和“ket2tot”,如果 ks1tot(i) 等于 ks1(j),则 ket1tot(i) 等于 ket1(j),否则 ket1tot(i)=0。 ket2tot 也是如此。

然后结合存储在向量“ks1tot”、“ket1tot”、“ket2tot”中的值,我计算向量(matFC)的哪些行包含我需要的值,通过乘以这些值,我得到最终结果( FCtot)。

所以我把这个例程变成了一个简单的程序,添加了一些初始行以模仿真实的程序。我的意思是我补充说:

1) 一个循环(在 i 上),它模仿我试图并行化的真实程序的外循环;

2) 我实现了每个线程处理不同文件的事实(所以我不应该有错误共享问题)

3) 我添加了另一个循环(在 k 上),模仿我多次调用该例程。

这里是代码(构成给我问题的原始子程序的部分在文中指出):

program evaluatefc
#ifdef _OPENMP
use omp_lib
#endif
implicit none   
integer::i,ii,j,jj,jjj,k,sizeks1,sizeks2,sizec,sizekstot,NR,NR1,maxnq
integer::line,ierr,fileunit,mythread,nfreqtot
real*8::FCtot,time1,time2
integer,allocatable,dimension(:)::ks1,ket1,ks2,ket2
integer,dimension(:),allocatable::c,kstot,ket1tot,ket2tot
real*8,allocatable,dimension(:)::matFC
character*15,allocatable,dimension(:)::matfileFC
character::fileFC*15
real*4::tstarting,tending
! This program was originally a subroutine 
! that takes in input 4 vectors, ks1, ks2, ket1, ket2
!---------------------------------------------------------------------------
! I initialize some values that in the original subroutine were computed by 
!the main program
allocate(matfileFC(3),stat=ierr)
matfileFC(1)='filea.dat'
matfileFC(2)='fileb.dat'
matfileFC(3)='filec.dat'
sizeks1=2
sizeks2=2
maxnq=11
allocate(ks1(sizeks1),stat=ierr)
allocate(ket1(sizeks1),stat=ierr)
allocate(ks2(sizeks2),stat=ierr)
allocate(ket2(sizeks2),stat=ierr)
nfreqtot=42
NR1=nfreqtot*(maxnq**2)+nfreqtot        
NR=nfreqtot*(maxnq**2)
allocate(matFC(NR),stat=ierr)
!Call two intrinsic to evaluate CPU and wall clock time
call cpu_time(time1)
tstarting=secnds(0.0)
!$OMP PARALLEL DO &
!$OMP DEFAULT(NONE) &
!$OMP firstprivate(sizeks1,sizeks2,maxnq,matfileFC,NR,NR1) &
!$OMP PRIVATE(i,ii,j,jj,k,ierr,mythread,fileunit,c,sizec,line,sizekstot) &
!$OMP PRIVATE(jjj,ket1,ks1,ket1tot,kstot,ket2,ks2,ket2tot,FCtot,matFC,fileFC)
do ii=1,3
   #ifdef _OPENMP
   mythread=OMP_GET_THREAD_NUM()
   #else
   mythread=10
   #endif
   fileFC=matfileFC(ii)
   ! Read some lines of a given file.
   fileunit=50+mythread
   open(unit=fileunit,name=fileFC,status='old',form='formatted')
   read(fileunit,*)!Do not read first line
   jjj=0
   do jj=1,NR1-1
       if(mod(jj,(maxnq**2+1)).eq.0) then
         read(fileunit,*)
       else
         jjj=jjj+1     
         read(fileunit,*)j,k,i,matFC(jjj)
   ! I actually need only the fourth valor of the line to be stored
       endif
   enddo
   close(fileunit)
   do k=1,10000000
       ! Again I initialize the abovementioned values that in the actual 
       ! subroutine are computed by the main program
       ks1(1)=mod(k,30)+1
       ks1(2)=mod(k,30)+2
       ks2(1)=mod(k,17)+1
       ks2(2)=mod(k,17)+3
       ket1(1)=mod(k,2)
       ket1(2)=mod(k,3)
       ket2(1)=mod(k,5)
       ket2(2)=mod(k,7)
       sizec=sizeks1+sizeks2
       allocate(c(sizec),stat=ierr)
       do i=1,sizeks1
           c(i)=ks1(i)
       enddo
       do i=sizeks1+1,sizec
          c(i)=ks2(i-sizeks1)
       enddo
       sizekstot=sizec
       do i=1,sizeks1
          do j=1,sizeks2
             if(ks1(i).eq.ks2(j)) then
               sizekstot=sizekstot-1
             endif
          enddo
       enddo
       allocate(kstot(sizekstot),stat=ierr)
       jjj=1
       i=1
       jj=0
       do i=1,sizec-1
           jjj=jjj+1
           do j=jjj,sizec
               if(c(i).eq.c(j)) then
                  exit   
               elseif(c(i).ne.c(j).and.j.eq.sizec) then
                  jj=jj+1
                  kstot(jj)=c(i)
               endif
           enddo
       enddo
       kstot(sizekstot)=c(sizec)
       allocate(ket1tot(sizekstot),stat=ierr)
       do i=1,sizekstot
           ket1tot(i)=0
       enddo
       allocate(ket2tot(sizekstot),stat=ierr)
       do i=1,sizekstot
           ket2tot(i)=0
       enddo
       do i=1,sizekstot
           do j=1,sizeks1
               if(kstot(i).eq.ks1(j))then
                  ket1tot(i)=ket1(j)
               endif
           enddo
       enddo
       do i=1,sizekstot
           do j=1,sizeks2
               if(kstot(i).eq.ks2(j))then
                     ket2tot(i)=ket2(j)
               endif
           enddo
       enddo
       FCtot=1
       do i=1,sizekstot
           line=(kstot(i)-1)*(maxnq)**2+ket1tot(i)*(maxnq)+ket2tot(i)+1
           FCtot=matFC(line)*FCtot
       enddo
       deallocate(c,stat=ierr)
       deallocate(kstot,stat=ierr)
       deallocate(ket1tot,stat=ierr)
       deallocate(ket2tot,stat=ierr)
   enddo
enddo
!$OMP END PARALLEL DO
call cpu_time(time2)
tending=secnds(tstarting)
write(*,*)
write(*,*)'CPU time is:'
write(*,*)time2-time1
write(*,*)
write(*,*)'Wall clock time is:'
write(*,*)tending
end program

我还是遇到了同样的问题,即使用 4 线程的挂钟时间比使用 1 线程的要大。

例如我得到(以秒为单位):

输入 Wtime CPU 时间

1 线程 20.37 20.37

4 线程 31.26 91.61

串行 19.64 19.64

我知道对 OMP 库的调用引入了开销,实际上 1 线程 OMP 程序比串行程序慢。但我不明白为什么 4 线程 OMP 代码更慢。

我在 Linux 上使用 Intel fortran 编译器 2013。

有什么建议吗?

感谢您在任何时候都可以致力于解决这个问题。

【问题讨论】:

  • 如果你缩进你的代码并展示一个代表你正在做什么的小测试程序,它会更容易阅读。请记住,您需要 OpenMP 方面的帮助(而不是我们许多人不太了解的理论化学方面的帮助),因此这将使我们能够更好地帮助您。话虽如此,您是否尝试过重新排序循环?英特尔编译器足够聪明,可以“猜测”哪个循环顺序更方便,前提是可以确定不会发生任何冲突。此外,如果可能,请尝试将分配/解除分配移到循环之外,这通常效率更高。
  • @Jorge Bellón 对不起,我现在已经缩进了我的代码。关于分配/解除分配,在我的真实程序 ks1 等中没有固定大小,所以我发现每次分配正确的大小很有用。无论如何,我进行了一项测试,结果表明避免分配/解除分配可以使代码更快,但不能解决 OpenMP 的问题。最后但并非最不重要的一点是,“显示一个小型测试程序”是什么意思?我发布的代码是一个转入工作代码的例程(如果您有一个像“filea.dat”这样的文件要读取),所以我不明白我应该在您看来发布的“测试程序”是什么。
  • 对于前面的评论,编译器可能会在 openmp 关闭时跳过冗余循环迭代。此外,如果在超线程上运行,如果你的 openmp 支持它,你应该设置亲和力。
  • cpu_time() 在这里没用。 system_clock() 是可移植的标准壁挂时间子程序(与 secnds 不同)。
  • 你的代码真的很长,整个问题也是如此。很长时间才能完整阅读。尝试减少文本(我已经删除了一些东西,但尝试删除物理的东西,它不相关)和代码。 我们需要更短的代码! 请参阅minimal reproducible example。如果不滚动就无法放入框内,则说明它太长了。

标签: parallel-processing fortran openmp fortran90


【解决方案1】:

好的,我自己解决了问题。

感谢大家的建议,尤其是@Jorge Bellón 和@High Performance Mark。

正如他们的 cmets 所说,问题实际上是大量的分配/解除分配。如果我将分配移出循环,或者至少如果我将它们放在第一个循环之后,我会得到“正常”的 OpenMP 行为,即线程数越大,挂钟时间越短。

对于上面的示例,使用 4 个线程的挂钟时间现在约为 7 秒。

感谢大家的帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-04
    • 2019-04-23
    • 2013-10-09
    • 2021-08-28
    • 2017-09-12
    相关资源
    最近更新 更多