好的,这是关于OpenMP指令的词法范围和动态范围的区别以及与变量作用域交互的内容。指令的词法范围是指在指令后的结构化块的开头和结尾之间的文本。动态范围是词法范围 加上 作为词法范围内语句执行结果的任何子程序的语句。所以在类似以下代码中:
Program stuff
Implicit None
Real, Dimension( 1:100 ) :: a
Call Random_number( a )
Call sub( a )
Contains
Subroutine sub( a )
Real, Dimension( : ), Intent( InOut ) :: a
Integer :: i
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 有许多规则,简单来说:
- 子程序参数的作用域从调用点继承,即如果您在词法范围的开头将它们设为私有,则它们保持私有,并且如果共享,则保持共享
- 子程序的本地变量默认为私有(因此上面的 i 是私有的,因为您希望如此),除非它们使用 SAVE 属性(显式或隐式)声明,否则它们将被共享。
根据我的经验,这可以让您完成大部分工作!使用动态范围与孤立指令结合使用是控制 OpenMP 程序的好方法,我不同意上述评论,我确实发现孤立的 workshare 指令非常有用!因此,您可以将所有内容组合起来,做到如下:
Program dot_test
Implicit None
Real, Dimension( 1:100 ) :: a, b
Real :: r
a = 1.0
b = 2.0
Call dot( a, b, r )
Write( *, * ) r
Contains
Subroutine dot( a, b, r )
Real, Dimension( : ), Intent( In ) :: a, b
Real, Intent( Out ) :: r
Real, Save :: s
Integer :: i
s = 0.0
Do i = 1, Size( a )
s = s + a( i ) * b( i )
End Do
r = s
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
在其中起到了作用,子程序和函数都被大量调用,因此避免不必要的重复内存分配非常重要。但是,如果使用SAVE
将它们推入堆栈(并影响性能),那么这就没有意义了。这可能需要进行测试。 - TTT!$omp private()
语句,但现在我明白我上一条评论说的是无意义的。OMP不会深入到子例程/函数中,这极大地简化了事情,并且似乎只需将例程指定为“递归的”就可以保证安全性。 - TTTSAVE
声明变量,那么线程之间可能会发生冲突。你可以在这些子程序中使用OpenMP指令:这种用法的术语是“孤立的”。 - M. S. B.