Sun Studio 12:Fortran 编程指南

第 10 章 并行化

本章概述多处理器并行化并介绍 Solaris SPARC 和 x86 多处理器平台上 Fortran 95 的功能。

另请参见 Rajat Garg 和 Ilya Sharapov 合著的《Techniques for Optimizing Applications: High Performance Computing》,Sun Microsystems BluePrints 出版 (http://www.sun.com/blueprints/pubs.html)。

10.1 基本概念

应用程序的并行化(或多线程) 是指对程序进行编译,使其能够在多处理器系统上或多线程环境中运行。并行化能使单个任务(如,DO 循环)运行于多个处理器(或线程)之上,从而有可能显著加快执行速度。

只有应用程序首先成为多线程程序,才能在 UltraTM 60、Sun EnterpriseTM Server 6500 或 Sun Enterprise Server 10000 等多处理器系统上高效运行。也就是说,需要对可并行执行的任务进行标识并重新编程,使其计算分布在多个处理器或线程上。

可以通过对 libthread 基元进行适当调用,来手动实现应用程序的多线程化。但可能需要进行大量的分析和重新编程工作。(有关更多信息,请参见 Solaris《 多线程编程指南》。)

Sun 编译器能自动生成在多处理器系统上运行的多线程对象代码。作为支持并行机制的主要语言元素,Fortran 编译器将关注焦点放在 DO 循环上。并行化可将循环的计算工作分配到多个处理器上,无需修改 Fortran 源程序

选择哪些循环进行并行化以及如何分配这些循环可以完全让编译器 (-autopar) 去决定,也可以由程序员使用源代码指令 (-explicitpar) 来显式指定,还可以采用组合方式 (-parallel) 来实现。


注 –

能用任何编译器并行化选项编译自行(显式)管理线程的程序。显式多线程(调用 libthread 基元)不能与用这些并行化选项编译的例程结合使用。


将程序中的所有循环都进行并行化处理并非都是有益的。只包含少量计算工作(与用于启动和同步并行任务的开销相比)的循环在并行化后,实际的运行速度可能更慢。另外,有些循环根本不能安全地进行并行化;由于语句或迭代间的依赖性,它们在并行运行时会计算出不同的结果。

隐式循环(例如,IF 循环和 Fortran 95 数组语法)和显式 DO 循环都可以由 Fortran 编译器自动进行并行化。

f95 能够检测出那些可以安全、有益地自动进行并行化的循环。但在大多数情况下,由于考虑到可能存在的隐藏副作用,这种分析肯定是保守的。(-loopinfo 选项可以显示哪些循环进行了并行化、哪些未进行并行化。)在循环前面插入源代码指令,可以显式地对分析施加影响,以控制如何并行化(或不并行化)特定的循环。但是,您随后需要负责确保循环的这种显式并行化不会导致错误的结果。

Fortran 95 编译器通过实现 OpenMP 2.0 Fortran API 指令来提供显式并行化。对于传统程序,f95 也可以接受较早的 Sun 和 Cray 风格的指令,但现在这些指令已经过时,不使用了。在 Fortran 95、C 和 C++ 中,OpenMP 已成为显式并行化的非正式标准,建议使用它来取代较早的指令风格。

有关 OpenMP 的信息,请参见《OpenMP API 用户指南》,或访问 OpenMP 网站 http://www.openmp.org

10.1.1 加速-期望目标

如果要使程序并行化以便该程序在四个处理器上运行,这样运行该程序花费的时间是否大致是在单个处理器上运行时所花费时间的四分之一(四倍加速)呢?

可能不行。可以证明(依据 Amdahl 法则):程序的总体加速性能严格受花在并行运行代码上的时间数量的限制。无论采用多少处理器都是如此。事实上,如果用 p 表示并行模式下花费的总程序执行时间的百分比,则理论加速限度为 100/(100–p);因此,如果只有 60% 的程序执行是以并行方式进行的,则最高加速倍数是 2.5,该值与处理器个数无关。对于只有四个处理器的情况,该程序的理论加速值(假设可以达到最高效率)只有 1.8 而不是 4。与总开销相比,实际加速较少。

如同优化一样,循环的选择至关重要。如果并行化的循环在总的程序执行时间中只占很小一部分,则只能获得微小的效果。要提高效率,必须并行化耗用大部分运行时间的循环。因此,第一步先要确定哪些循环是主要的,然后从此开始。

问题量在确定并行运行程序片段并进而确定加速性能中也起着重要作用。增加问题量会增加循环中完成的工作量。三重嵌套循环将会使工作量呈立方级数递增。如果并行化外层嵌套循环,则少量增加问题量便能使性能有显著提高(与未并行化性能相比)。

10.1.2 程序并行化步骤

此处列出了应用程序并行化的常规步骤:

  1. 优化。使用适当的编译器选项集,以在单个处理器上获得最佳串行性能。

  2. 配置文件。使用典型测试数据,确定程序的性能配置文件。标识最主要的循环。

  3. 基准测试。确定串行测试结果是准确的。使用这些结果以及性能配置文件作为基准。

  4. 并行化。使用选项和指令组合编译并生成并行化的可执行文件。

  5. 验证。在单个处理器和单个线程上运行并行化的程序,并检查结果,以找出可能在其中出现的不稳定性和编程错误。(将 $PARALLEL$OMP_NUM_THREADS 设置为 1;请参见10.1.5 线程数)。

  6. 测试。在几个处理器上执行各种运行以检查结果。

  7. 基准测试。在专用系统上用不同数目的处理器进行性能测量。测量性能随问题量变化的变化情况(可量测性)。

  8. 重复步骤 4 到 7。基于性能对并行化方案进行改进。

10.1.3 数据依赖性问题

不是所有的循环都可并行化。在多个处理器上以并行方式运行循环通常会导致迭代执行次序紊乱。而且,只要循环中存在数据依赖性,以并行方式执行循环的多个处理器便有可能相互干扰。

会引起数据依赖性问题的情况包括递归、约简、间接寻址以及依赖于数据的循环迭代。

10.1.3.1 依赖于数据的循环

您可以重新编写循环来消除数据依赖性,使其可以并行化。但需要进行大量的重构工作。

以下是一些通用规则:

这些是进行并行化的一般条件。在确定是否并行化循环时,编译器的自动并行化分析会考虑附加条件。但是,可以使用指令显式地强制并行化循环,甚至是那些包含抑制因素和产生错误结果的循环。

10.1.3.2 递归

在循环的某一次迭代中设置并在后续迭代中使用的变量会导致产生交叉迭代依赖性或递归。循环中的递归要求迭代以正确顺序执行。例如:


   DO I=2,N
      A(I) = A(I-1)*B(I)+C(I)
   END DO

必须在上一迭代中计算出 A(I) 的值,方能在当前迭代中(作为 A(I-1))使用。要产生正确的结果,迭代 I 必须先完成,迭代 I+1 方可执行。

10.1.3.3 约简

约简操作可将数组中的元素约简为单个值。例如,在对数组元素求和并送入单个变量时,需要在每次迭代时更新该变量:


   DO K = 1,N
     SUM = SUM + A(I)*B(I)
   END DO

如果以并行方式运行该循环的每个处理器均取得了迭代的一些子集,这些处理器将会相互干扰,从而覆盖 SUM 中的值。为使之正常工作,每个处理器每次必须执行一次求和,但顺序并不重要。

编译器会将某些常见的约简操作视为特例进行处理。

10.1.3.4 间接寻址

如果向在循环中用下标(下标值未知)标出的数组存储数据,会导致循环依赖性。例如,如果在索引数组中存在重复的值,间接寻址会依赖于顺序:


   DO L = 1,NW
     A(ID(L)) = A(L) + B(L)
   END DO

在示例中,ID 中重复的值会造成 A 中的元素被覆盖。在串行情况下,最后存储的是最终值。在并行情况下,顺序是不确定的。所使用的 A(L) 值(旧的或更新后的)依赖于顺序。

10.1.4 编译以实现并行化

Sun Studio 编译器本身支持将 OpenMP 并行化模型作为主并行化模型。有关 OpenMP 并行化的详细信息,参见《OpenMP API 用户指南》。Sun 和 Cray 风格的并行化涉及传统的应用程序,当前的 Sun Studio 编译器不再支持这些风格的并行化。

表 10–1 Fortran 95 并行化选项

选项 

标志 

自动(单独

-autopar

自动和约简 

-autopar -reduction

显示并行化哪些循环 

-loopinfo

显示显式情况下的警告 

-vpara

在栈中分配局部变量 

-stackvar

编译以实现 OpenMP 并行化 

-xopenmp

选项注释:

10.1.5 线程数

PARALLEL(或 OMP_NUM_THREADS)环境变量用来控制程序可以使用的线程的最大数量。设置该环境变量可将程序能够使用的最大线程数告之运行时系统。缺省值为 1。一般会将 PARALLELOMP_NUM_THREADS 变量设置为目标平台上可用虚拟处理器的数量。

下例展示如何设置环境变量:


demo% setenv OMP_NUM_THREADS 4       C shell


demo$ OMP_NUM_THREADS=4               Bourne/Korn shell
demo$ export OMP_NUM_THREADS

在本例中,将 PARALLEL 设置为 4,可以最多使用四个线程来执行程序。如果目标机有四个可用的处理器,这些线程将分别映射到独立的处理器。如果可用处理器数少于四个,则一些线程必须与其他线程在同一处理器上运行,这样可能会降低性能。

SunOSTM 操作系统命令 psrinfo(1M) 显示系统上可用的处理器列表:


demo% psrinfo
0      on-line   since 03/18/2007 15:51:03
1      on-line   since 03/18/2007 15:51:03
2      on-line   since 03/18/2007 15:51:03
3      on-line   since 03/18/2007 15:51:03

10.1.6 栈、栈大小和并行化

执行程序可为执行该程序的初始线程维护一个主内存栈,还可为每个辅助线程维护不同的栈。栈为临时内存地址空间,用来保存子程序调用期间的参数和 AUTOMATIC 变量。

主栈的缺省大小约为 8 兆字节。Fortran 编译器通常会将局部变量和数组作为 STATIC 进行分配(而不是在栈中)。但是,-stackvar 选项强制在栈内分配所有局部变量和数组(就像它们是 AUTOMATIC 变量一样)。建议在并行化时使用 -stackvar,因为它可增强优化程序在循环中并行化子程序调用的功能。对于包含子程序调用的显式并行化循环,-stackvar必需的。(请参见《Fortran 用户指南》中对 -stackvar 的介绍。)

使用 C shell (csh) 时,limit 命令会显示当前主栈大小,而且会对其进行设置:


demo% limit             C shell example
cputime       unlimited
filesize       unlimited
datasize       2097148 kbytes
stacksize       8192 kbytes            <- current main stack size
coredumpsize       0 kbytes
descriptors       64
memorysize       unlimited
demo% limit stacksize 65536       <- set main stack to 64Mb
demo% limit stacksize
stacksize       65536 kbytes

对于 Bourne 或 Korn shell,相应的命令为 ulimit


demo$ ulimit -a         Korn Shell example
time(seconds)        unlimited
file(blocks)         unlimited
data(kbytes)         2097148
stack(kbytes)        8192
coredump(blocks)     0
nofiles(descriptors) 64
vmemory(kbytes)      unlimited
demo$ ulimit -s 65536
demo$ ulimit -s
65536

多线程程序的每个辅助线程都有自己的线程栈。该栈模拟初始线程栈,但对于线程是唯一的。线程的 PRIVATE 数组和变量(线程的局部变量)在线程栈中分配。缺省大小在 64 位 SPARC 和 64 位 x86 平台上为 8 兆字节,在其他平台上为 4 兆字节。此大小是通过 STACKSIZE 环境变量设置的:


demo% setenv STACKSIZE 8192    <- Set thread stack size to 8 Mb    C shell
                          -or-
demo$ STACKSIZE=8192           Bourne/Korn Shell
demo$ export STACKSIZE

对于某些已并行的 Fortran 代码,可能需要将线程栈大小设置为比缺省值大的值。但是,除了反复进行错误试验,不可能知道其确切大小,特别是如果涉及到专用/局部数组就更是如此。如果栈大小太小不足以运行线程,程序将会因段故障而中止。

10.2 自动并行化

使用 -autopar 选项,f95 编译器会自动查找可以有效并行化的 DO 循环。然后对这些循环进行转换,将其迭代均匀分布在可用的处理器上。编译器会生成实现这一目标所需的线程调用。

10.2.1 循环并行化

编译器的依赖性分析会将 DO 循环转换成可并行化的任务。编译器可能会重构循环,分离出将要串行运行的不可并行化部分。然后将工作均匀分布在可用的处理器上。每个处理器执行不同的迭代块。

例如,对于四个 CPU 和具有 1000 次迭代的并行化循环,每个线程将执行含有 250 次迭代的程序块。

处理器 1 执行迭代 

至 

250 

处理器 2 执行迭代 

251 

至 

500 

处理器 3 执行迭代 

501 

至 

750 

处理器 4 执行迭代 

751 

至 

1000 

只有不依赖于计算执行顺序的循环才能成功进行并行化。编译器的依赖性分析拒绝对那些具有内在数据依赖性的循环进行并行化。如果不能完全确定循环中的数据流,编译器会保守行事,不进行并行化。另外,如果能确定性能增益抵不上总开销,编译器也不会对循环进行并行化。

注意:编译器总是选择对使用静态循环调度的循环进行并行化-即将循环中的工作拆分到多个等效的迭代块中。其他调度方案可以用本章后面所述的显式并行化指令来指定。

10.2.2 数组、标量和纯标量

自动并行化角度看,需要一些新定义:

示例:数组/标量:


      dimension a(10)
      real m(100,10), s, u, x, z
      equivalence ( u, z )
      pointer ( px, x )
      s = 0.0
      ...

ma 都是数组变量;s 是纯标量。变量 uxzpx 是标量变量,但不是标量。

10.2.3 自动并行化标准

不具有任何交叉迭代数据依赖性的 DO 循环由 -autopar 自动并行化。自动并行化的一般标准是:

10.2.3.1 直观依赖性

编译器可以自动消除显示的引用以在循环中创建数据依赖性。这种转换有许多,其中之一会利用某些数组的专用版本。通常,如果编译器能确定这种数组在原始循环中只是作为临时存储使用,它便会这样做。

示例:使用 -autopar,通过专用数组消除了依赖性:


      parameter (n=1000)
      real a(n), b(n), c(n,n)
      do i = 1, 1000             <--Parallelized
        do k = 1, n
          a(k) = b(k) + 2.0
        end do
        do j = 1, n-1
          c(i,j) = a(j+1) + 2.3
        end do
      end do
      end

在此例中,外层循环被并行化,并在独立的处理器上运行。虽然内层循环对数组 a 的引用看起来会导致数据依赖性,但编译器会生成数组的临时专用副本,使外层循环迭代变得独立。

10.2.3.2 自动并行化抑制因素

自动并行化过程中,如果以下条件下成立,编译器不会对循环进行并行化:

10.2.3.3 嵌套循环

在多线程多处理器环境中,对循环嵌套中最外层循环(而不是最内层循环)进行并行化最有效。由于并行处理通常涉及相对较大的循环开销,所以并行化最外层循环会最大程度地减少开销并增加每个线程完成的工作量。在自动并行化情况下,编译器从最外层嵌套循环开始进行循环分析,然后继续向内进行直至找到可并行化的循环。一旦嵌套中的某个循环被并行化,便会略过该并行循环内所包含的循环。

10.2.4 具有约简操作的自动并行化

将一个数组转换为一个标量的计算称为约简操作。典型的约简操作有矢量元素的求和或求积。违反循环中计算标准的约简操作不能在各次迭代间以累积方式改变标量变量。

示例:向量元素的约简求和:


      s = 0.0
      do i = 1, 1000
        s = s + v(i)
      end do
      t(k) = s

但是,对于某些操作,如果约简是阻止并行化的唯一因素,仍然可以对循环进行并行化。常见约简操作出现频率很高,因而编译器能够将其视为特例进行并行化。

自动并行化分析不包括约简操作的识别,除非随 -autopar-parallel 一同指定了 -reduction 编译器选项。

如果某一可并行化的循环包含表 10–2 中列出的某一项约简操作,则当指定了 -reduction 时,编译器将会对其进行并行化。

10.2.4.1 识别的约简操作

下表列出了编译器识别的约简操作。

表 10–2 识别的约简操作

数学运算 

Fortran 语句模板 

求和 

s = s + v(i)

求积 

s = s * v(i)

点积 

s = s + v(i) * u(i)

最小值 

s = amin( s, v(i))

最大值 

s = amax( s, v(i))

OR

do i = 1, n

b = b .or. v(i)

end do

AND

b = .true.

do i = 1, n

b = b .and. v(i)

end do

非零元素计数 

k = 0

do i = 1, n

if(v(i).ne.0) k = k + 1

end do

识别所有形式的 MINMAX 函数。

10.2.4.2 数值准确性和约简操作

由于以下原因,浮点型数字的求和或求积约简操作可能不准确:

在一些情况下,该误差是不能接受的。

示例:舍入,求介于 –1 和 +1 之间的 100,000 个随机数之和:


demo% cat t4.f
      parameter ( n = 100000 )
      double precision d_lcrans, lb / -1.0 /, s, ub / +1.0 /, v(n)
      s = d_lcrans ( v, n, lb, ub ) ! Get n random nos. between -1 and +1
      s = 0.0
      do i = 1, n
        s = s + v(i)
      end do
      write(*, ’(" s = ", e21.15)’) s
      end
demo% f95 -O4 -autopar -reduction t4.f

结果会随着处理器的个数而变化。下表展示了介于 – 1 和 +1 之间的 100,000 个随机数之和。

处理器数 

输出 

s = 0.568582080884714E+02

s = 0.568582080884722E+02

s = 0.568582080884721E+02

s = 0.568582080884724E+02

在这种情况下,对于随机开始的数据而言,10-14 阶的舍入误差是可以接受的。有关更多信息,请参见 Sun《数值计算指南》。

10.3 显式并行化

本部分介绍 f95 识别的源代码指令,这些指令用来显式指示要并行化的循环以及要使用的策略。

Fortran 95 编译器现在完全支持 OpenMP Fortran API 作为主并行化模型。有关其他信息,请参见《OpenMP API 用户指南》。

SPARC 平台上的 Sun Studio 编译器不再支持传统的 Sun 风格和 Cray 风格的并行化指令,x86 平台上的编译器不接受这些指令。

程序的显式并行化需要预先分析并深入理解应用程序代码以及共享内存并行化概念。

在 DO 循环之前紧接着放置的指令标记这些循环将要进行并行化。用 -xopenmp 进行编译,可以识别 OpenMP Fortran 95 指令并生成并行化的 DO 循环代码。并行化指令是用来指示编译器并行化(或不并行化)指令后面的 DO 循环的注释行。指令又称编译指示

在选择要标记进行并行化的循环时,要小心行事。即使存在并行运行时会导致循环计算结果错误的数据依赖性,编译器也会为所有标有并行化指令的循环生成线程化的并行代码。

如果用 libthread 基元编写自己的多线程代码,请使用任何编译器并行化选项-编译器不能并行化已使用线程库用户调用并行化的代码。

10.3.1 可并行化的循环

如果以下条件成立,则循环适用于显式并行化:

10.3.1.1 作用域规则:专用和共享

专用变量或数组归循环的单次迭代专用。在一次迭代中赋予专用变量或数组的值不会传播给循环内的其他任何迭代。

共享变量或数组在所有迭代间共享。在某次迭代中赋予共享变量或数组的值为循环内的其他迭代所见。

如果显式并行化的循环包含共享引用,则必须确保共享不会造成正确性问题。编译器在共享变量的更新或访问上不同步。

如果在一个循环内将某变量指定为专用,并且其唯一一次初始化位于另一循环中,则该变量的值在此循环中可能保持未定义。

10.3.1.2 循环中的子程序调用

循环(或从已调用例程内调用的任何子程序)中的子程序调用可能会导致产生数据依赖性,这种数据依赖性很容易被忽略,而不通过调用链深入分析数据和控制流。尽管最好是并行化执行大量工作的最外层循环,但这些循环往往正是涉及子程序调用的循环。

由于这种过程间的分析很困难并且会大大增加编译时间,所以自动并行化模式不会尝试这样做。对于显式并行化,编译器会为标有 PARALLEL DODOALL 指令的循环生成并行化代码,即使它包含子程序调用。确保此循环以及此循环包括的所有循环(包括被调用的子程序)中不存在任何数据依赖性仍是程序员的责任。

不同线程多次调用某个例程会造成问题,这些问题源自对互相干扰的局部静态变量的引用。使例程中的所有局部变量均为自动而不是静态可防止这种情况。此时,子程序的每次调用都会在栈中保留自己唯一的局部变量存储,任何两次调用均不会相互干扰。

AUTOMATIC 语句中列出子程序局部变量或用 -stackvar 选项编译子程序,通过这两种方法中的任一种,可以使子程序局部变量变为驻留在栈中的自动变量。但是,DATA 语句中初始化的局部变量必须进行改写,才能在实际赋值中进行初始化。


注 –

将局部变量分配给栈会造成栈溢出。有关如何增加栈大小的信息,请参见10.1.6 栈、栈大小和并行化


10.3.1.3 显式并行化抑制因素

一般而言,如果您显式指导编译器对循环进行并行化,编译器就会执行。但也有例外情况-存在一些编译器不进行并行化的循环。

下面是可检测到的主要抑制因素,这些抑制因素可以防止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

在此,循环被并行化,但在警告中诊断出可能的循环携带依赖性。但要注意,编译器并不能诊断出所有循环依赖性。

10.3.1.4 显式并行化时的 I/O

在下列情况下,可以在并行执行的循环中执行 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 的函数的调用,将会造成运行时错误。

10.3.2 OpenMP 并行化指令

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 用户指南》。

10.4 环境变量

并行化使用多个环境变量:OMP_NUM_THREADS、SUNW_MP_WARN、SUNW_MP_THR_IDLE、SUNW_MP_PROCBIND、STACKSIZE 和其他环境变量。《OpenMP API 用户指南》中对它们进行了介绍。

10.5 调试并行化的程序

Fortran 源代码:


    real x / 1.0 /, y / 0.0 /
    print *, x/y
    end
    character  string*5, out*20
    double precision value
    external exception_handler
    i = ieee_handler(’set’, ’all’, exception_handler)
    string = ’1e310’
    print *, ’Input string ’, string, ’ becomes: ’, value
    print *, ’Value of 1e300 * 1e10 is:’, 1e300 * 1e10
    i = ieee_flags(’clear’, ’exception’, ’all’, out)
    end

    integer function exception_handler(sig, code, sigcontext)
    integer sig, code, sigcontext(5)
    print *, ’*** IEEE exception raised!’
    return
    end

运行时输出:


*** IEEE exception raised!
 Input string 1e310 becomes:  Infinity
 Value of 1e300 * 1e10 is: Inf
 Note: Following IEEE floating-point traps enabled;
   see ieee_handler(3M):
 Inexact;  Underflow;  Overflow;  Division by Zero;  Invalid
   Operand;
 Sun’s implementation of IEEE arithmetic is discussed in
  the Numerical Computation Guide.
Debugging Parallelized Programs

调试已并行化的程序需要做一些额外工作。下列方案提出了处理该任务的方法。

10.5.1 调试时的首要步骤

有一些步骤可以直接进行尝试以确定错误原因。

替换:


    DO I=1,N
      ...
      CALL SNUBBER(I)
      ...
    ENDDO

使用:


      DO I1=1,N
      I=I1
      ...
      CALL SNUBBER(I)
      ...
    ENDDO

10.6 进阶读物

下列书籍提供更多的信息: