本部分介绍 f95 识别的源代码指令,这些指令用来显式指示要并行化的循环以及要使用的策略。
Fortran 95 编译器现在完全支持 OpenMP Fortran API 作为主并行化模型。有关其他信息,请参见《OpenMP API 用户指南》。
SPARC 平台上的 Sun Studio 编译器不再支持传统的 Sun 风格和 Cray 风格的并行化指令,x86 平台上的编译器不接受这些指令。
程序的显式并行化需要预先分析并深入理解应用程序代码以及共享内存并行化概念。
在 DO 循环之前紧接着放置的指令标记这些循环将要进行并行化。用 -xopenmp 进行编译,可以识别 OpenMP Fortran 95 指令并生成并行化的 DO 循环代码。并行化指令是用来指示编译器并行化(或不并行化)指令后面的 DO 循环的注释行。指令又称编译指示。
在选择要标记进行并行化的循环时,要小心行事。即使存在并行运行时会导致循环计算结果错误的数据依赖性,编译器也会为所有标有并行化指令的循环生成线程化的并行代码。
如果用 libthread 基元编写自己的多线程代码,请勿使用任何编译器并行化选项-编译器不能并行化已使用线程库用户调用并行化的代码。
循环是 DO 循环,而不是 DO WHILE 循环或 Fortran 95 数组语法。
循环内每个迭代的数组变量值不依赖于循环内任何其他迭代的数组变量值。
如果循环会改变某一标量变量,该变量值在循环终止后不会被使用。此类标量变量在循环终止后不能保证具有定义的值,因为编译器不会自动确保其正确回存。
对于每次迭代,循环内调用的任何子程序都不引用或改变其他任何迭代的数组变量值。
DO 循环索引必须是整数。
专用变量或数组归循环的单次迭代专用。在一次迭代中赋予专用变量或数组的值不会传播给循环内的其他任何迭代。
共享变量或数组在所有迭代间共享。在某次迭代中赋予共享变量或数组的值为循环内的其他迭代所见。
如果显式并行化的循环包含共享引用,则必须确保共享不会造成正确性问题。编译器在共享变量的更新或访问上不同步。
如果在一个循环内将某变量指定为专用,并且其唯一一次初始化位于另一循环中,则该变量的值在此循环中可能保持未定义。
循环(或从已调用例程内调用的任何子程序)中的子程序调用可能会导致产生数据依赖性,这种数据依赖性很容易被忽略,而不通过调用链深入分析数据和控制流。尽管最好是并行化执行大量工作的最外层循环,但这些循环往往正是涉及子程序调用的循环。
由于这种过程间的分析很困难并且会大大增加编译时间,所以自动并行化模式不会尝试这样做。对于显式并行化,编译器会为标有 PARALLEL DO 或 DOALL 指令的循环生成并行化代码,即使它包含子程序调用。确保此循环以及此循环包括的所有循环(包括被调用的子程序)中不存在任何数据依赖性仍是程序员的责任。
不同线程多次调用某个例程会造成问题,这些问题源自对互相干扰的局部静态变量的引用。使例程中的所有局部变量均为自动而不是静态可防止这种情况。此时,子程序的每次调用都会在栈中保留自己唯一的局部变量存储,任何两次调用均不会相互干扰。
在 AUTOMATIC 语句中列出子程序局部变量或用 -stackvar 选项编译子程序,通过这两种方法中的任一种,可以使子程序局部变量变为驻留在栈中的自动变量。但是,DATA 语句中初始化的局部变量必须进行改写,才能在实际赋值中进行初始化。
将局部变量分配给栈会造成栈溢出。有关如何增加栈大小的信息,请参见10.1.6 栈、栈大小和并行化。
一般而言,如果您显式指导编译器对循环进行并行化,编译器就会执行。但也有例外情况-存在一些编译器不进行并行化的循环。
下面是可检测到的主要抑制因素,这些抑制因素可以防止对 DO 循环进行显式并行化:
DO 循环嵌套在已并行化的另一 DO 循环内。
该例外情况也适用于间接嵌套。如果显式并行化包含子例程调用的循环,那么,即使要求编译器并行化该子例程中的循环,这些循环在运行时也不会以并行方式运行。
流控制语句允许跳出 DO 循环。
循环的索引变量受副作用影响,例如被等价。
通过使用 -vpara 和 -loopinfo 进行编译,可以得到诊断消息,指出在显式并行化循环过程中编译器是否检测到问题。
下表列出了编译器检测到的典型并行化问题:
表 10–3 显式并行化问题
问题 |
已并行化 |
警告消息 |
---|---|---|
循环嵌套在并行化了的另一循环内。 |
否 |
否 |
循环在并行化循环体内调用的某个子例程中。 |
否 |
否 |
流控制语句允许跳出循环。 |
否 |
是 |
循环的索引变量受副作用影响。 |
是 |
否 |
循环中的某变量具有循环携带依赖性。 |
是 |
是 |
在循环中使用 I/O 语句-通常是不明智的,因为输出顺序无法预料。 |
是 |
否 |
示例:嵌套循环:
... !$OMP PARALLEL DO do 900 i = 1, 1000 ! Parallelized (outer loop) do 200 j = 1, 1000 ! Not parallelized, no warning ... 200 continue 900 continue ... |
示例:子例程中已并行化的循环:
program main ... !$OMP PARALLEL DO do 100 i = 1, 200 <-parallelized ... call calc (a, x) ... 100 continue ... subroutine calc ( b, y ) ... !$OMP PARALLEL DO do 1 m = 1, 1000 <-not parallelized ... 1 continue return end |
在此例中,由于子例程本身是以并行方式运行的,所以子例程中的循环未被并行化。
示例:跳出循环:
!$omp parallel do do i = 1, 1000 ! <- Not parallelized, error issued ... if (a(i) .gt. min_threshold ) go to 20 ... end do 20 continue ... |
如果标记进行并行化的循环外有转跳,编译器会发出诊断错误。
示例:循环中的某个变量具有循环携带依赖性:
demo% cat vpfn.f real function fn (n,x,y,z) real y(*),x(*),z(*) s = 0.0 !$omp parallel do private(i,s) shared(x,y,z) do i = 1, n x(i) = s s = y(i)*z(i) enddo fn=x(10) return end demo% f95 -c -vpara -loopinfo -openmp -O4 vpfn.f "vpfn.f", line 5: Warning: the loop may have parallelization inhibiting reference "vpfn.f", line 5: PARALLELIZED, user pragma used |
在此,循环被并行化,但在警告中诊断出可能的循环携带依赖性。但要注意,编译器并不能诊断出所有循环依赖性。
在下列情况下,可以在并行执行的循环中执行 I/O:
来自不同线程的输出相互交错(程序输出是非确定的),这一点并不重要。
可以确保并行执行循环的安全性。
示例:循环中有 I/O 语句
!$OMP PARALLEL DO PRIVATE(k) do i = 1, 10 ! Parallelized k = i call show ( k ) end do end subroutine show( j ) write(6,1) j 1 format(’Line number ’, i3, ’.’) end demo% f95 -openmp t13.f demo% setenv PARALLEL 4 demo% a.out |
Line number 9. Line number 4. Line number 5. Line number 6. Line number 1. Line number 2. Line number 3. Line number 7. Line number 8. |
但递归的 I/O,即 I/O 语句包含对本身执行 I/O 的函数的调用,将会造成运行时错误。
OpenMP 是用于多处理器平台的并行编程模型,即将成为 Fortran 95、C 和 C++ 应用程序的标准编程实践方案。它是 Sun Studio 编译器的首选并行编程模型。
要启用 OpenMP 指令,请用 -openmp 选项标志进行编译。Fortran 95 OpenMP 指令用类似注释的 !$OMP 标记标识,标记后紧跟指令名和从属子句。
!$OMP PARALLEL 指令标识程序中的并行区域。!$OMP DO 指令标识并行区域内即将并行化的 DO 循环。可以将这些指令合并成单个 !$OMP PARALLEL DO 指令,该指令必须紧跟在 DO 循环之前。
OpenMP 规范包括许多用于在程序并行区域中共享和同步工作的指令,还包括用于数据作用域和控制的从属子句。
OpenMP 和传统的 Sun 风格指令之间的一个主要不同点是,OpenMP 需要显式数据作用域以专用或共享方式使用,并还要提供自动作用域功能。
有关更多信息(包括使用 Sun 和 Cray 并行化指令转换传统程序的指导原则),请参见《OpenMP API 用户指南》。