跳过导航链接 | |
退出打印视图 | |
Oracle Solaris Studio 12.3:Fortran 用户指南 Oracle Solaris Studio 12.3 Information Library (简体中文) |
可使用源代码指令(一种 Fortran 注释格式)将有关专门的优化或并行化选择的特定信息传递给编译器。编译器指令有时也称为 pragma。编译器可以识别一组通用指令和并行化指令。f95 还可以处理 OpenMP 共享内存多重处理指令。
特定于 f95 的指令在4.8 指令中进行说明。附录 C中完整总结了 f95 可以识别的所有指令。
注 - 指令并不是 Fortran 标准的一部分。
Fortran 通用指令的各种形式包括:
!$PRAGMA keyword ( a [ , a ] … ) [ , keyword ( a [ , a ] … ) ] ,…
!$PRAGMA SUN keyword ( a [ , a ] … ) [ , keyword ( a [ , a ] … ) ] ,…
变量 keyword 标识特定的指令。此外也可以使用额外的参数或子选项。(某些指令要求使用额外的关键字 SUN,如上所示。)
通用指令使用以下语法:
在第一列中,使用以下任一注释指示符:c、C、! 或 *
对于 f95 自由格式,! 是唯一可识别的注释指示符 (!$PRAGMA)。本章中的示例采用 Fortran 95 自由格式。
后面七个字符为大写或小写的 $PRAGMA(字符之间没有空格)。
对于自由格式的源程序,使用 ! 注释指示符的指令可以出现在行中的任意位置。
请遵循以下约束:
与 Fortran 文本一样,在前八个字符之后,空格将被忽略,而且大写和小写字母等效。
由于它是注释,因此指令无法继续,但如果需要,您可以连续使用许多 !$PRAGMA 行。
如果注释符合以上语法,则它应该包含一个或多个编译器可识别的指令;如果不符合以上语法,系统会发出一条警告。
C 预处理程序 cpp 将扩展注释行或指令行中的宏符号定义;Fortran 预处理程序 fpp 不扩展注释行中的宏。fpp 会识别合法的 f95 指令,并允许在指令关键字外部进行有限的替换。但是,在处理需要关键字 SUN 的指令时要格外小心。cpp 会将小写的 sun 替换为预定义的值。另外,如果您定义了一个 cpp 宏 SUN,则它可能会与 SUN 指令关键字发生冲突。一般规则是,如果源文件将由 cpp 或 fpp 来处理,那么应以混合大小写来拼写这些 pragma,如下所示:
!$PRAGMA Sun UNROLL=3。
编译 .F 文件时,添加 -Usun 可能也是一种解决方法。
Fortran 编译器可识别以下通用指令:
表 2-2 通用 Fortran 指令摘要
|
C() 指令指定其参数为外部函数。它与 EXTERNAL 声明等效,但有一点例外,与普通外部名称不同,Fortran 编译器在这些参数名称的后面不附加下划线。有关详细信息,请参见《Fortran 编程指南》中的“C-Fortran 接口”一章。
在每个包含特定函数引用的子程序中,用于该函数的 C() 指令应该出现在对该函数的第一次引用之前。
示例-为 C 编译 ABC 和 XYZ:
EXTERNAL ABC, XYZ !$PRAGMA C(ABC, XYZ)
此指令导致编译器在解析特定调用时,忽略在通用过程接口中出现的指定哑元参数名称的类型、种类和等级。
例如,在下面的过程接口中,指令指定 SRC 可以是任何数据类型,但 LEN 可以为 KIND=4 或 KIND=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_64。S 的实际类型并不能确定要调用哪个例程。对于基于参数类型、种类或等级来调用特定库例程的包装器来说,这可大大简化为其编写通用接口的工作。
请注意,无法在该指令中指定假定形状数组、Fortran 指针或可分配数组的哑元参数。如果未指定名称,则该指令将应用于过程的所有哑元参数,但假定形状数组、Fortran 指针或可分配数组的哑元参数除外。
UNROLL 指令要求您在 !$PRAGMA 后指定 SUN。
!$PRAGMA SUN UNROLL=n 指令指示编译器在其优化过程中将以下循环解开 n 次。(只有当编译器分析认为此类解开有必要时,它才会解开循环。)
n 是正整数。选项有:
如果 n=1,则优化器不得解开任何循环。
如果 n>1,则优化器可以解开循环 n 次。
如果实际解开了任何循环,那么可执行文件会变大。有关详细信息,请参见《Fortran 编程指南》中有关性能与优化的章节。
示例—解开循环两次:
!$PRAGMA SUN UNROLL=2
WEAK 指令定义一个符号,其优先级比以前定义的相同符号要低。此 pragma 主要用于源文件中以创建库。如果链接程序无法解析弱符号,它并不生成错误消息。
!$PRAGMA WEAK (name1 [=name2])
WEAK (name1) 将 name1 定义为弱符号。如果链接程序没有找到 name1 的定义,它不会生成错误消息。
WEAK (name1=name2) 将 name1 定义为弱符号以及 name2 的别名。
如果程序调用 name1,但没有对其进行定义,那么链接程序将使用库中的定义。但是,如果程序定义了自己的 name1 版本,那么将采用程序的定义,而不是使用库中 name1 的弱全局定义。如果程序直接调用 name2,则将使用库中的定义;重复的 name2 定义将导致错误。有关更多信息,请参见 Solaris《链接程序和库指南》。
OPT 指令设置子程序的优化级别,这将覆盖编译命令行中指定的级别。该指令必须紧挨在目标子程序前面出现,并且仅应用于该子程序。例如:
!$PRAGMA SUN OPT=2 SUBROUTINE smart(a,b,c,d,e) ...etc
在使用指定 -O4 的 f95 命令编译以上内容时,该指令将覆盖此级别,并以 -O2 级别编译子例程。 除非该例程后面另有一个指令,否则下一个子程序将以 -O4 级别编译。
还必须使用 -xmaxopt[=n] 选项编译例程以便识别该指令。此编译器选项为 PRAGMA OPT 指令指定最大的优化值:如果 PRAGMA OPT 指定的优化级别大于 -xmaxopt 级别,则使用 -xmaxopt 级别。
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
有关优化的更多信息,请参见《Fortran 编程指南》。
-xprefetch 选项标志(3.4.153 -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)
有关预取指令的详细信息,另请参见《C 用户指南》或《SPARC Architecture Manual, Version 9》。
ASSUME 指令向编译器提供有关程序中某些点的条件的提示。这些断言可以帮助编译器设定其优化策略。程序员也可以在执行过程中使用这些指令检查程序的有效性。ASSUME 有两种格式。
“点断言”ASSUME 的语法是:
!$PRAGMA ASSUME (expression [,probability])
另外,“范围断言”ASSUME 的语法是:
!$PRAGMA BEGIN ASSUME [expression [, probability) block of statements !$PRAGMA END ASSUME
请使用点断言格式来声明编译器可在程序中的该点采用的条件。而使用范围断言格式来声明在语句封闭范围内有效的条件。范围断言中的 BEGIN 和 END 对必须正确嵌套。
必需的 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 指令的表达式子句。(请注意,它们的名称前面有两个下划线。)
|
在将来的版本中,特殊内部函数的列表可能会扩展。
可与 -xassume_control 编译器选项结合使用。(请参见3.4.105 -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 编译上述示例,则在由于行程计数为零或负数而没有执行循环时,将会发出一条运行时警告。
并行化指令显式地请求编译器尝试并行处理该指令后面的 DO 循环或代码区域。其语法与一般指令不同。只有在使用 -openmp 选项进行编译时才能识别并行化指令。有关 Fortran 并行化的详细信息,请参见《OpenMP API 用户指南》和《Fortran 编程指南》。
Fortran 编译器支持 OpenMP 3.1 共享内存并行化模型。传统的 Sun 和 Cray 并行化指令现已过时,不应再使用它们。
Fortran 编译器将 OpenMP Fortran 共享内存多处理 API 识别为首选的并行编程模型。该 API 是由 OpenMP 体系结构审查委员会 (http://www.openmp.org) 指定的。
要启用 OpenMP 指令,您必须使用命令行选项 -xopenmp 进行编译。(请参见3.4.145 -xopenmp[={ parallel|noopt|none}]。)
有关 f95 接受的 OpenMP 指令的更多信息,请参见《OpenMP API 用户指南》。
注 - 传统的 Sun 和 Cray 风格的并行化指令现已过时。首选使用 OpenMP 并行化 API。有关如何从传统的 Sun/Cray 指令迁移到 OpenMP 模型的信息,请参见《OpenMP API 用户指南》。
!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 编译器选项(请参见3.4.126 -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 也可以应用于数组赋值语句 FORALL 或 WHERE 结构。如果特定的循环存在多个指令(例如 IVDEP? 和 UNROLL),编译器会尽可能服从所有指令。