通常,编译器没有足够的信息来判断并行化的合法性或有益性。编译器支持 pragma,使程序员能够有效地并行化循环,否则编译器很难或根本无法处理这些循环。本节中其他地方详细介绍的 Sun 特定的旧 MP pragma 对于 OpenMP 标准来说已过时。有关该标准的指令的信息,请参见《OpenMP API 用户指南》。
Sun 特定的旧 MP pragma 已过时,并且不再受支持。但是,编译器改为支持 OpenMP 3.0 标准指定的 API。有关标准的指令的迁移信息,请参见《OpenMP API 用户指南》。
有两个串行 pragma,均适用于 for 循环:
#pragma MP serial_loop
#pragma MP serial_loop_nested
#pragma MP serial_loop pragma 向编译器指示:不会自动并行化下一个 for 循环。
#pragma MP serial_loop_nested pragma 向编译器指示:下一个 for 循环以及该 for 循环的作用域内嵌套的任何 for 循环均不自动并行化。
这些 pragma 的作用域以 pragma 开始,并在下列先出现的项结束:下一个块的开头、当前块内的下一个 for 循环或当前块的结尾。
Sun 特定的旧 MP pragma 已过时,并且不再受支持。但是,编译器改为支持 OpenMP 3.0 标准指定的 API。有关标准的指令的迁移信息,请参见《OpenMP API 用户指南》。
有一个并行 pragma:#pragma MP taskloop [options]。
MP taskloop pragma 可以根据需要带下列一个或多个参数。
maxcpus (number_of_processors)
private (list_of_private_variables)
shared (list_of_shared_variables)
readonly (list_of_readonly_variables)
storeback (list_of_storeback_variables)
savelast
reduction (list_of_reduction_variables)
schedtype (scheduling_type)
这些 pragma 的作用域以 pragma 开始,并在下列先出现的项结束: 下一块的开始部分、当前块内部的下一个 for 循环、当前块的结尾。该 pragma 应用于 pragma 作用域结束前的下一个 for 循环。
每个 MP taskloop pragma 只能指定一个选项;但是,pragmas 是累积的,并应用于 pragmas 作用域中的下一个 for 循环:
#pragma MP taskloop maxcpus(4) #pragma MP taskloop shared(a,b) #pragma MP taskloop storeback(x) |
这些选项在其所应用的 for 循环之前可能会多次出现。如果存在冲突选项,编译器将发出警告消息。
MP taskloop pragma 应用于当前块内部的下一个 for 循环。并行化 C 不存在并行化 for 循环的嵌套。
除非另有禁止,否则 MP taskloop pragma 建议编译器应并行化指定的 for 循环。
任何具有不规则控制流和未知循环迭代增量的 for 循环均不能进行并行化。例如,包含 setjmp、longjmp、exit、abort、return、goto、labels 和 break 的 for 循环均不能并行化。
特别重要的是,具有迭代间依赖性的 for 循环可以进行显式并行化。这意味着,如果为此类循环指定了一个 MP taskloop pragma,除非 for 循环被取消资格,否则编译器将完全遵循该 pragma。用户有责任确保此类显式并行化不会导致错误结果。
如果为 for 循环指定了 serial_loop(或 serial_loop_nested)pragma 和 taskloop pragma,则将使用最后指定的 pragma。
请看以下示例:
#pragma MP serial_loop_nested for (i=0; i<100; i++) { # pragma MP taskloop for (j=0; j<1000; j++) { ... } } |
i 循环将不并行化,但是 j 循环可能并行化。
#pragma MP taskloop maxcpus (number_of_processors) 指定要用于此循环的处理器数(如果可能)。
maxcpus 的值必须为正整数。如果 maxcpus 等于 1,指定的循环将串行执行。(请注意,将 maxcpus 设置为 1 相当于指定 serial_loop pragma。)将比较 maxcpus 的值与 PARALLEL 环境变量的解释值,使用较小的值。在未指定环境变量 PARALLEL 的情况下,该 pragma 被解释为具有值 1。
如果为一个 for 循环指定了多个 maxcpus pragma,将使用最后指定的 pragma。
循环中使用的变量可分类为 private、shared、reduction 或 readonly 变量。变量只能属于其中一种分类。通过显式 pragma 只能将变量分类为 reduction 或 readonly 变量。请参见 #pragma MP taskloop reduction 和 #pragma MP taskloop readonly。通过显式 pragma 或以下缺省作用域规则可将变量分类为 private 或 shared 变量。
private 变量的值是处理 for 循环的某些迭代的每个处理器的私有值。换句话说,在 for 循环的一次迭代中赋给 private 变量的值不会传送给处理该 for 循环的其他迭代的其他处理器。而 shared 变量的当前值可处理 for 循环的迭代的所有处理器访问。处理循环迭代的一个处理器赋给 shared 变量的值可能会被处理循环的其他迭代的其他处理器识别。通过使用 #pragma MP taskloop 指令显式并行化并包含共享变量引用的循环,必须确保值的共享不会导致正确性问题(如竞争情况)。对显式并行化的循环中的共享变量的更新和访问,编译器不提供同步。
在分析显式并行化的循环时,编译器使用以下“缺省作用域规则”来确定变量是 private 变量还是 shared 变量:
如果变量未通过 pragma 显式分类,已声明为指针或数组,并且仅在循环内部使用数组语法进行引用,则该变量缺省分类为 shared 变量。否则,将被分类为 private 变量。
循环索引变量始终被视为 private 变量和返回存储变量。
强烈建议将显式并行化的 for 循环中使用的所有变量显式分类为 shared、private、reduction 或 readonly 变量,以避免使用“缺省作用域规则”。
由于编译器对共享变量的访问不执行同步,因此在对包含数组引用等的循环使用 MP taskloop pragma 之前必须格外谨慎。如果此类显式并行化的循环中存在迭代间数据依赖性,则其并行执行会导致错误结果。编译器不一定能够检测到此类潜在问题情况并发出警告消息。无论如何,编译器不会禁止具有潜在共享变量问题的循环的显式并行化。
#pragma MP taskloop private (list_of_private_variables)
使用此 pragma 指定所有应视为此循环私有变量的变量。此循环中使用但未显式指定为 shared、readonly 或 reduction 变量的所有其他变量,按缺省作用域规则的定义,为 shared 变量或 private 变量。
private 变量的值是处理循环的某些迭代的每个处理器的私有值。换句话说,处理循环迭代的一个处理器赋给 private 变量的值不会传送给处理该循环的其他迭代的其他处理器。private 变量在循环的每次迭代开始时没有初始值,必须在循环的迭代内部第一次使用它之前,在该迭代内部设置为一个值。如果某个循环包含一个在设置之前使用其值的显式声明 private 变量,则执行包含该循环的程序将导致不确定的行为。
#pragma MP taskloop shared (list_of_shared_variables)
使用此 pragma 指定所有应视为此循环的 shared 变量的变量。循环中使用的但未显式指定为 private、readonly、storeback 或 reduction 变量的所有其他变量,按缺省作用域规则的定义,为 shared 变量或 private 变量。
shared 变量的当前值可被处理 for 循环的迭代的所有处理器访问。处理循环迭代的一个处理器赋给 shared 变量的值可能会被处理循环的其他迭代的其他处理器识别。
#pragma MP taskloop readonly (list_of_readonly_variables)
readonly 变量是一类特殊的共享变量,不在循环的任何迭代中进行修改。使用此 pragma 向编译器指示,它可以对处理循环迭代的每个处理器使用该变量值的单独副本。
#pragma MP taskloop storeback (list_of_storeback_variables)
使用此 pragma 指定所有应视为 storeback 变量的变量。
storeback 变量的值在循环中计算,并且计算的值在循环终止后使用。storeback 变量的最后一个循环迭代值在循环终止后使用。当变量为私有变量时,此类变量可以很好地通过此指令显式声明为 storeback 变量,通过将变量显式声明为私有变量或通过使用缺省作用域规则均可。
请注意,storeback 变量的返回存储操作发生在显式并行化循环的最后一次迭代中,而无论该迭代是否更新 storeback 变量的值。换句话说,处理循环最后一次迭代的处理器可能并不是当前包含 storeback 变量的最后更新值的同一处理器。请看以下示例:
#pragma MP taskloop private(x) #pragma MP taskloop storeback(x) for (i=1; i <= n; i++) { if (...) { x=... } } printf (“%d”, x); |
在上例中,通过 printf() 调用输出的 storeback 变量 x 的值可能与通过 i 循环的串行版本输出的值不同,原因是,在显式并行化情况下,处理循环最后一次迭代(当 i==n 时)的处理器(即执行 x 的返回存储操作的处理器)可能不是当前包含 x 的最后更新值的同一处理器。编译器将尝试发出警告消息,提醒用户注意此类潜在问题。
在显式并行化的循环中,作为数组引用的变量不视为 storeback 变量。因此,如果需要此类返回存储操作(例如,如果作为数组引用的变量已声明为私有变量),则将作为数组引用的变量包括在 list_of_storeback_variables 中很重要。
#pragma MP taskloop savelast
使用此 pragma 指定要视为返回存储变量的所有私有变量。此 pragma 的语法如下:
#pragma MP taskloop savelast
通常,使用这种形式很方便,无需在将每个变量声明为返回存储变量时列出循环的每个私有变量。
#pragma MP taskloop reduction (list_of_reduction_variables) 指定出现在 reduction 变量列表中的所有变量均将视为循环的 reduction 变量。reduction 变量的部分值可由处理循环迭代的每个处理器单独计算,其最终值可从其所有部分值中计算。有了 reduction 变量列表,便于编译器识别循环是否为约简循环,从而允许为其生成并行约简代码。请看以下示例:
#pragma MP taskloop reduction(x) for (i=0; i<n; i++) { x = x + a[i]; } |
变量 x 是 (sum) 约简变量,i 循环是 (sum) 约简循环。
Solaris Studio ISO C 编译器支持多种 pragma,这些 pragma 可与 taskloop pragma 配合使用,以控制给定循环的循环调度策略。此 pragma 的语法是:
#pragma MP taskloop schedtype (scheduling_type)
此 pragma 可用来指定要用来调度并行化循环的特定 scheduling_type。Scheduling_type 可为以下类型之一:
static
在 static 调度中,循环的所有迭代均匀地分布在参与处理的所有处理器中。请看以下示例:
#pragma MP taskloop maxcpus(4) #pragma MP taskloop schedtype(static) for (i=0; i<1000; i++) { ... } |
在以上示例中,四个处理器中的每个处理器将处理循环的 250 次迭代。
self [(chunk_size)]
在 self 调度中,每个参与处理的处理器处理固定次数的迭代(称为“块大小”),直到循环的所有迭代均已处理完毕为止。可选的 chunk_size 参数指定要使用的“块大小”。Chunk_size 必须为正整数常量或整型变量。如果指定为变量,chunk_size 在循环开始时求出的值必须为正整数值。如果未指定这个可选的参数,或者其值不为正数,编译器将选择要使用的块大小。请看以下示例:
#pragma MP taskloop maxcpus(4) #pragma MP taskloop schedtype(self(120)) for (i=0; i<1000; i++) { ... } |
在以上示例中,分配给每个参与处理的处理器的迭代次数按工作请求顺序依次为:
120、120、120、120、120、120、120、120、40。
gss [(min_chunk_size)]
在 guided self 调度中,每个参与处理的处理器处理可变次数的迭代(称为“最小块大小”),直到循环的所有迭代均已处理完毕为止。可选的 min_chunk_size 参数指定使用的每个可变块大小至少必须为 min_chunk_size。Min_chunk_size 必须为正整数常量或整型变量。如果指定为变量,min_chunk_size 在循环开始时求出的值必须为正整数值。如果未指定这个可选的参数,或者其值不为正数,编译器将选择要使用的块大小。请看以下示例:
#pragma MP taskloop maxcpus(4) #pragma MP taskloop schedtype(gss(10)) for (i=0; i<1000; i++) { ... } |
在以上示例中,分配给每个参与处理的处理器的迭代次数按工作请求顺序依次为:
250、188、141、106、79、59、45、33、25、19、14、11、10、10、10。
factoring [(min_chunk_size)]
在 factoring 调度中,每个参与处理的处理器处理可变次数的迭代(称为“最小块大小”),直到循环的所有迭代均已处理完毕为止。可选的 min_chunk_size 参数指定使用的每个可变块大小至少必须为 min_chunk_size。Min_chunk_size 必须为正整数常量或整型变量。如果指定为变量,min_chunk_size 在循环开始时求出的值必须为正整数值。如果未指定这个可选的参数,或者其值不为正数,编译器将选择要使用的块大小。请看以下示例:
#pragma MP taskloop maxcpus(4) #pragma MP taskloop schedtype(factoring(10)) for (i=0; i<1000; i++) { ... } |
在以上示例中,分配给每个参与处理的处理器的迭代次数按工作请求顺序依次为:
125, 125, 125, 125, 62、62、62, 62, 32, 32, 32, 32, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10