Fortran OpenMP与子程序和函数

9
免责声明:我非常确定这个问题已经有答案了,但我和另一个人一直在努力搜索却没有找到。
我有一段代码,看起来像这样:
      PROGRAM main
!$omp parallel do
!$omp private(somestuff) shared(otherstuff)
      DO i=1,n
        ...
        CALL mysubroutine(args)
        ...
        a=myfunction(moreargs)
        ...
      ENDDO
!$omp end parallel do
      END PROGRAM
      SUBROUTINE mysubroutine(things)
      ...
      END SUBROUTINE
      FUNCTION myfunction(morethings)
      ...
      END FUNCTION

我无法确定如何处理子程序和函数中变量的私有、共享、约简等子句。我怀疑答案可能有一些微妙之处,因为变量可能已经以许多种方式声明并共享。所以,假设所有主程序关心的变量都在它或共享模块中定义,并且对这些变量进行的任何OMP操作可以在主代码中处理。子程序和函数使用其中一些变量,并具有一些自己的变量。因此,我认为问题归结为如何处理它们本地变量的子句。


展示一个这样的函数的例子。如果局部变量不是“save”,那么它们就不是问题。 - Vladimir F Героям слава
请查看此链接:http://stackoverflow.com/questions/22381795/is-there-an-easy-way-to-prepare-fortran-code-for-parallel-invocation。您需要研究的关键词是“线程安全”和“线程安全”过程。 - Vladimir F Героям слава
@VladimirF 为了澄清,其他两个的代码我都有,所以更多是关于语法而非线程安全的问题。 SAVE 在其中起到了作用,子程序和函数都被大量调用,因此避免不必要的重复内存分配非常重要。但是,如果使用 SAVE 将它们推入堆栈(并影响性能),那么这就没有意义了。这可能需要进行测试。 - TTT
我不明白你的评论。你的整个问题都是关于线程安全的,不是吗?你有什么样的语法想法?你不能访问不在作用域内的变量。如果你有代码,请展示出来!否则你的问题就太宽泛了。 - Vladimir F Героям слава
我还在阅读相关资料。我曾认为需要在子程序中添加!$omp private()语句,但现在我明白我上一条评论说的是无意义的。OMP不会深入到子例程/函数中,这极大地简化了事情,并且似乎只需将例程指定为“递归的”就可以保证安全性。 - TTT
1
我不确定你的评论意思,但如果你在从OpenMP并行区调用的过程中使用SAVE声明变量,那么线程之间可能会发生冲突。你可以在这些子程序中使用OpenMP指令:这种用法的术语是“孤立的”。 - M. S. B.
1个回答

11

好的,这是关于OpenMP指令的词法范围和动态范围的区别以及与变量作用域交互的内容。指令的词法范围是指在指令后的结构化块的开头和结尾之间的文本。动态范围是词法范围 加上 作为词法范围内语句执行结果的任何子程序的语句。所以在类似以下代码中:

Program stuff
   Implicit None
   Real, Dimension( 1:100 ) :: a
   Call Random_number( a )
   !$omp parallel default( none ) shared( a )
   Call sub( a )
   !$omp end parallel
Contains
   Subroutine sub( a )
      Real, Dimension( : ), Intent( InOut ) :: a
      Integer :: i
      !$omp do
      Do i = 1, Size( a )
         a( i ) = 2.0 * a( i )
      End Do
   End Subroutine Sub
End Program stuff

(完全未经测试,直接在此处编写)由!$omp parallel引发的并行区域的词汇范围仅限于此

   Call sub( a )

动态范围是调用和子程序内容,而 !$omp do 是一个孤立指令的例子(即不在另一个指令的词法范围内,但在动态范围内)。请参见https://computing.llnl.gov/tutorials/openMP/#Scoping了解另一个示例。为什么这很重要?因为只能显式地将变量限定于词法范围中的实体(例如本例中的数组 a),但对于由于执行动态范围而定义的实体,您无法这样做!相反,OpenMP 有许多规则,简单来说:

  1. 子程序参数的作用域从调用点继承,即如果您在词法范围的开头将它们设为私有,则它们保持私有,并且如果共享,则保持共享
  2. 子程序的本地变量默认为私有(因此上面的 i 是私有的,因为您希望如此),除非它们使用 SAVE 属性(显式或隐式)声明,否则它们将被共享。

根据我的经验,这可以让您完成大部分工作!使用动态范围与孤立指令结合使用是控制 OpenMP 程序的好方法,我不同意上述评论,我确实发现孤立的 workshare 指令非常有用!因此,您可以将所有内容组合起来,做到如下:

Program dot_test
  Implicit None
  Real, Dimension( 1:100 ) :: a, b
  Real :: r
  a = 1.0
  b = 2.0
  !$omp parallel default( none ) shared( a, b, r )
  Call dot( a, b, r )
  Write( *, * ) r
  !$omp end parallel
Contains
  Subroutine dot( a, b, r )
    Real, Dimension( : ), Intent( In    ) :: a, b
    Real,                 Intent(   Out ) :: r
    Real, Save :: s
    Integer :: i
    !$omp single
    s = 0.0
    !$omp end single
    !$omp do reduction( +:s )
    Do i = 1, Size( a )
       s = s + a( i ) * b( i )
    End Do
    !$omp end do
    !$omp single
    r = s
    !$omp end single
  End Subroutine dot
End Program dot_test
Wot now? gfortran -std=f95 -fopenmp -O -Wall -Wextra dot.f90 
Wot now? export OMP_NUM_THREADS=3
Wot now? ./a.out
   200.000000    
   200.000000    
   200.000000  

这种简单情况因模块变量和公共块有点复杂,所以不要使用全局变量...但如果你必须使用它们,默认情况下它们是共享的,除非声明为线程专用。请参见

https://computing.llnl.gov/tutorials/openMP/#THREADPRIVATE

以获取示例。


很好的例子,我错了。你展示的save变量的处理也很有用。在Chandra的《OpenMP并行编程》中有一个关于孤立指令的很好的讨论。 - Vladimir F Героям слава

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接