Oracle® Developer Studio 12.5:Fortran 用户指南

退出打印视图

更新时间: 2016 年 6 月
 
 

2.3 指令

可使用源代码指令(一种 Fortran 注释格式)将有关专门的优化或并行化选择的特定信息传递给编译器。编译器指令有时也称为 pragma。编译器可识别一组常规指令和并行化指令,包括 OpenMP 指令。

特定于 f95 的指令在指令中进行说明。Fortran 指令摘要中完整总结了 f95 可以识别的所有指令。


注 - 指令并不是 Fortran 标准的一部分。

2.3.1 通用指令

Fortran 通用指令的各种形式包括:

!$PRAGMA keyword ( a [ , a ] ) [ , keyword ( a [ , a ] ) ] ,

!$PRAGMA SUN keyword ( a [ , a ] … ) [ , keyword ( a [ , a ] … ) ] ,…

变量 keyword 标识特定的指令。此外也可以使用额外的参数或子选项。(某些指令要求使用额外的关键字 SUN,如上所示。)

通用指令使用以下语法:

  • 在第一列中,使用以下任一注释指示符:cC!*

  • 对于 f95 自由格式,! 是唯一可识别的注释指示符 (!$PRAGMA)。本章中的示例采用 Fortran 95 自由格式。

  • 后面七个字符为大写或小写的 $PRAGMA(字符之间没有空格)。

  • 对于自由格式的源程序,使用 ! 注释指示符的指令可以出现在行中的任意位置。

请遵循以下约束:

  • 与 Fortran 文本一样,在前八个字符之后,空格将被忽略,而且大写和小写字母等效。

  • 由于它是注释,因此指令无法继续,但如果需要,您可以连续使用许多 !$PRAGMA 行。

  • 如果注释符合以上语法,则它应该包含一个或多个编译器可识别的指令;如果不符合以上语法,系统会发出一条警告。

  • C 预处理程序 cpp 将扩展注释行或指令行中的宏符号定义;Fortran 预处理程序 fpp 不扩展注释行中的宏。fpp 会识别合法的 f95 指令,并允许在指令关键字外部进行有限的替换。但是,在处理需要关键字 SUN 的指令时要格外小心。cpp 会将小写的 sun 替换为预定义的值。另外,如果您定义了一个 cppSUN,则它可能会与 SUN 指令关键字发生冲突。一般规则是,如果源文件将由 cppfpp 来处理,那么应以混合大小写来拼写这些 pragma,如下所示:

    !$PRAGMA Sun UNROLL=3

    编译 .F 文件时,添加 -Usun 可能也是一种解决方法。

Fortran 编译器可识别以下通用指令:

表 2  通用 Fortran 指令摘要
C 指令
!$PRAGMA C(list)
将一系列外部函数的名称声明为 C 语言例程。
IGNORE_TKR 指令
!$PRAGMA IGNORE_TKR {name {, name} ...}
在解析特定调用时,编译器会忽略在通用过程接口中出现的指定哑元参数名称的类型、种类和等级。
UNROLL 指令
!$PRAGMA SUN UNROLL=n
建议编译器将下面的循环解开为指定的长度 n
WEAK 指令
!$PRAGMA WEAK(name[=name2])
name 声明为弱符号,或者声明为 name2 的别名。
OPT 指令
!$PRAGMA SUN OPT=n
将子程序的优化级别设置为 n
PIPELOOP 指令
!$PRAGMA SUN PIPELOOP=n
断言下面的循环中在间隔为 n 的迭代之间存在依赖性。
PREFETCH 指令
!$PRAGMA SUN_PREFETCH_READ_ONCE(name)
!$PRAGMA SUN_PREFETCH_READ_MANY(name)
!$PRAGMA SUN_PREFETCH_WRITE_ONCE(name)
!$PRAGMA SUN_PREFETCH_WRITE_MANY(name)
请求编译器为名称引用生成预取指令。(需要使用 -xprefetch 选项,缺省情况下启用该选项。编译时使用 —xprefetch=no 可以禁用预取指令。目标体系结构也必须支持预取指令,而且编译器优化级别必须大于 —xO2。)
ASSUME 指令
!$PRAGMA [BEGIN} ASSUME (expression [,probability])
!$PRAGMA END ASSUME
断言编译器可假定程序中某些点处的条件为真。

2.3.1.1 C 指令

C() 指令指定其参数为外部函数。它与 EXTERNAL 声明等效,但有一点例外,与普通外部名称不同,Fortran 编译器在这些参数名称的后面不附加下划线。

在每个包含特定函数引用的子程序中,用于该函数的 C() 指令应该出现在对该函数的第一次引用之前。

示例-为 C 编译 ABCXYZ

       EXTERNAL ABC, XYZ
!$PRAGMA C(ABC, XYZ)

2.3.1.2 IGNORE_TKR 指令

此指令导致编译器在解析特定调用时,忽略在通用过程接口中出现的指定哑元参数名称的类型、种类和等级。

例如,在下面的过程接口中,指令指定 SRC 可以是任何数据类型,但 LEN 可以为 KIND=4KIND=8。接口块为通用过程名称定义了两个特定过程。此示例以 Fortran 95 自由格式说明。

INTERFACE BLCKX

SUBROUTINE BLCK_32(LEN,SRC)
  REAL SRC(1)
!$PRAGMA IGNORE_TKR SRC
  INTEGER (KIND=4) LEN
END SUBROUTINE

SUBROUTINE BLCK_64(LEN,SRC)
  REAL SRC(1)
!$PRAGMA IGNORE_TKR SRC
  INTEGER (KIND=8) LEN
END SUBROUTINE

END INTERFACE

The subroutine call:

INTEGER L
REAL S(100)
CALL BLCKX(L,S)

在进行正常编译时,BLCKX 调用将调用 BLCK_32;在使用 -xtypemap=integer:64 进行编译时,将调用 BLCK_64S 的实际类型并不能确定要调用哪个例程。对于基于参数类型、种类或等级来调用特定库例程的包装器来说,这可大大简化为其编写通用接口的工作。

请注意,无法在该指令中指定假定形状数组、Fortran 指针或可分配数组的哑元参数。如果未指定名称,则该指令将应用于过程的所有哑元参数,但假定形状数组、Fortran 指针或可分配数组的哑元参数除外。

2.3.1.3 UNROLL 指令

UNROLL 指令要求您在 !$PRAGMA 后指定 SUN

!$PRAGMA SUN UNROLL=n 指令指示编译器在其优化过程中将以下循环解开 n 次。(只有当编译器分析认为此类解开有必要时,它才会解开循环。)

n 是正整数。选项包括:

  • 如果 n=1,则优化器解开任何循环。

  • 如果 n>1,则优化器可以解开循环 n 次。

如果实际解开了任何循环,那么可执行文件会变大。

示例—解开循环两次:

!$PRAGMA SUN UNROLL=2

2.3.1.4 WEAK 指令

WEAK 指令定义一个符号,其优先级比以前定义的相同符号要低。此 pragma 主要用于源文件中以创建库。如果链接程序无法解析弱符号,它并不生成错误消息。

!$PRAGMA WEAK (name1 [=name2])

WEAK (name1) 将 name1 定义为弱符号。如果链接程序没有找到 name1 的定义,它不会生成错误消息。

WEAK (name1=name2) 将 name1 定义为弱符号以及 name2 的别名。

如果程序调用 name1,但没有对其进行定义,那么链接程序将使用库中的定义。但是,如果程序定义了自己的 name1 版本,那么将采用程序的定义,而不是使用库中 name1 的弱全局定义。如果程序直接调用 name2,则将使用库中的定义;重复的 name2 定义将导致错误。有关更多信息,请参见Oracle Solaris 11.3 链接程序和库指南

2.3.1.5 OPT 指令

OPT 指令要求您在 !$PRAGMA 后指定 SUN

OPT 指令设置子程序的优化级别,这将覆盖编译命令行中指定的级别。该指令必须紧挨在目标子程序前面出现,并且仅应用于该子程序。例如:

!$PRAGMA SUN OPT=2
        SUBROUTINE smart(a,b,c,d,e)
        ...等

在使用指定 -O4f95 命令编译以上内容时,该指令将覆盖此级别,并以 -O2 级别编译子例程。除非该例程后面另有一个指令,否则下一个子程序将以 -O4 级别编译。

还必须使用 -xmaxopt[=n] 选项编译例程以便识别该指令。此编译器选项为 PRAGMA OPT 指令指定最大的优化值:如果 PRAGMA OPT 指定的优化级别大于 -xmaxopt 级别,则使用 -xmaxopt 级别。

2.3.1.6 PIPELOOP[=n] 指令

PIPELOOP=n 指令要求您在 !$PRAGMA 后面指定 SUN

此指令必须紧挨在 DO 循环前面出现。n 是正整数常量或零,它向优化器断言循环迭代之间是否存在依赖性。值零表示循环中没有迭代间的(即循环带有的)依赖性,优化器可以对循环执行任意管道处理。正值 n 表示,循环的第 I 次迭代与第 (I-n) 次迭代之间存在依赖性,每次最多只能对 n 个迭代进行管道处理。(如果未指定 n,则缺省为 0)

C    We know that the value of K is such that there can be no
C    cross-iteration dependencies (E.g. K>N)
!$PRAGMA SUN PIPELOOP=0
      DO I=1,N
       A(I)=A(I+K) + D(I)
       B(I)=B(I) + A(I)
      END DO

2.3.1.7 PREFETCH 指令

-xprefetch 选项标志(–xprefetch[=a[,a]])可启用一组 PREFETCH 指令,这些指令指示编译器在支持预取的处理器上为指定的数据元素生成预取指令。

!$PRAGMA SUN_PREFETCH_READ_ONCE(name)
!$PRAGMA SUN_PREFETCH_READ_MANY(name)
!$PRAGMA SUN_PREFETCH_WRITE_ONCE(name)
!$PRAGMA SUN_PREFETCH_WRITE_MANY(name)

有关预取指令的详细信息,另请参见Oracle Developer Studio 12.5:C 用户指南或《SPARC Architecture Manual, Version 9》。

2.3.1.8 ASSUME 指令

ASSUME 指令向编译器提供有关程序中某些点的条件的提示。 这些断言可以帮助编译器设定其优化策略。程序员也可以在执行过程中使用这些指令检查程序的有效性。ASSUME 有两种格式。

“点断言”ASSUME 的语法是:

!$PRAGMA ASSUME (expression [,probability])

另外,“范围断言”ASSUME 的语法是:

!$PRAGMA BEGIN ASSUME [expression [, probability)
     block of statements
!$PRAGMA END ASSUME

请使用点断言格式来声明编译器可在程序中的该点采用的条件。而使用范围断言格式来声明在语句封闭范围内有效的条件。范围断言中的 BEGINEND 对必须正确嵌套。

必需的 expression 是一个布尔表达式,该表达式可在程序中的该点求值,并且其中不包含用户定义的运算符或函数调用(下面列出的除外)。

可选的 probability 值是一个介于 0.0 和 1.0 之间的实数或者是整数 0 或 1,它给出表达式为真的可能性。probability 的值为 0.0(或 0)意味着永远不会为真;值为 1.0(或 1)则意味着始终为真。如果没有指定,则认为表达式很有可能为真,但并不一定为真。probability 为 0 或 1 以外其他值的断言是非必然断言。类似地,probability 正好为 0 或 1 的断言是必然断言

例如,如果程序员知道 DO 循环的长度始终大于 10,000,则为编译器提供该提示可使之生成更好的代码。通常,以下循环在使用 ASSUME pragma 时比不使用时运行得要快。

!$PRAGMA BEGIN ASSUME(__tripcount().GE.10000,1) !! a big loop
        do i = j, n
           a(i) = a(j) + 1
        end do
!$PRAGMA END ASSUME

有两个内部函数专用于 ASSUME 指令的表达式子句。(请注意,它们的名称前面有两个下划线。)

__branchexp()
用于紧挨在分支转移语句(带有布尔控制表达式)前面的点断言。它与控制分支转移语句的布尔表达式生成相同的结果。
__tripcount()
生成紧跟在指令后面的或指令所包含的循环的行程计数。在用于点断言时,指令后面的语句必须位于 DO 的第一行。在用于范围断言时,它应用于最外层的封闭循环。

在将来的版本中,特殊内部函数的列表可能会扩展。

可与 -xassume_control 编译器选项结合使用。 (请参见–xassume_control[=keywords])。例如,在使用 -xassume_control=check 进行编译时,如果行程计数变为小于10,000,则上述示例将生成一条警告。

如果使用 -xassume_control=retrospective 进行编译,则在程序终止时会生成一个摘要报告,指出所有断言是真还是假。有关 -xassume_control 的详细信息,请参见 f95 手册页。

另一个示例:

!$PRAGMA ASSUME(__tripcount.GT.0,1)
       do i=n0, nx

如果使用 -xassume_control=check 编译上述示例,则在由于行程计数为零或负数而没有执行循环时,将会发出一条运行时警告。

2.3.2 并行化指令

只有在使用 -openmp 进行编译时才能识别 OpenMP 并行化指令。有关 OpenMP 并行化的详细信息,请参见Oracle Developer Studio 12.5:OpenMP API 用户指南

Fortran 编译器支持 OpenMP API(用于实现共享内存并行化)版本 4.0。传统的 Sun 和 Cray 并行化指令现已过时,不应再使用它们。

2.3.2.1 OpenMP 并行化指令

Fortran 编译器将 OpenMPAPI(用于实现共享内存并行化)识别为首选的并行编程模型。该 API 是由 OpenMP 体系结构审查委员会 (http://www.openmp.orghttp://www.openmp.org ) 指定的。

要启用 OpenMP 指令,您必须使用命令行选项 -xopenmp 进行编译。(请参见–xopenmp[={parallel|noopt|none}]。)

有关 f95 接受的 OpenMP 指令的更多信息,请参见Oracle Developer Studio 12.5:OpenMP API 用户指南

2.3.2.2 传统的 Sun/Cray 并行化指令


注 -  传统的 Sun 和 Cray 风格的并行化指令现已过时。首选使用 OpenMP 并行化 API。

2.3.3 IVDEP 指令

!DIR$ IVDEP 指令指示编译器忽略其在循环中找到的部分或全部对数组引用的循环附带依赖性,使编译器能够在其他循环之间执行各种循环优化,例如微向量化、分布、软件流水化,否则这些优化将无法实现。当用户知道这些依赖性无关紧要或者实际上永远不会发生时,可以使用该指令。

例如:

	 DO I = 1, N 
	   A(V(I)) = A(V(I)) + C(I) 
	 END DO        

在此循环中,存在几处对 A(V(I)) 的循环附带依赖性,因为 V(I) 可能会将重复值传递给索引 A,对循环重新排序可能会产生不同的结果。但如果已知 V 仅包含不同的值,则可以安全地对循环进行重新排序,并且可以使用 IVDEP 指令来实现优化。

可以使用 —xivdep 编译器选项(请参见–xivdep[=p])来禁用或确定 IVDEP 指令的解释。

IVDEP 指令的某些传统解释仅能断言不存在向后循环附带依赖性。Fortran 编译器的缺省值是 —xivdep=loop,表示 IVDEP 指令断言不存在假定的循环依赖性。

下面的示例解释向后依赖性与向前依赖性。

 	 do i = 1, n			! BACKWARD LOOP-CARRIED DEPENDENCE 
		... = a(i-1)		 ! S1 
	    a(i) = ...		 ! S2 
	 end do  
	 do i = 1, n			! FORWARD LOOP-CARRIED DEPENDENCE 
	    a(i) = ...		! S3 
		... = a(i-1)		! S4 
	 end do       

第一个循环有一个从 S2 到 S1 的向后循环附带依赖性,第二个循环中有一个从 S3 到 S4 的向前循环附带依赖性。由于第二个循环只有一个向前依赖性,因此可以安全地进行分布和/或微向量化,而第一个循环则不能。

下面是使用缺省值 -xivdep=loop 的情况下使用 IVDEP 的示例:

integer target a(n)
integer, pointer :: p(:), q(:)
!DIR$ IVDEP
do i = 1, n
	 p(i) = q(i) 
	 a(i) = a(i-1)
end do        

p(i)q(i) 之间以及 p(i)a(*) 之间的假定依赖性将被忽略,而 a(i)a(i-1) 之间的显式依赖性则不被忽略。该循环可分割为两个循环,所生成的 p(i) = q(i) 循环可以实现微向量化。

IVDEP 指令应用于紧随其后的 DO 循环。在指令与循环之间不允许有其他代码。!DIR$ IVDEP 也可以应用于数组赋值语句 FORALLWHERE 结构。如果特定的循环存在多个指令(例如 IVDEPUNROLL),编译器会尽可能服从所有指令。