ISO C 别名通常可防止循环并行化。存在两个对同一存储单元的可能引用时,将产生别名。请看以下示例:
示例 3-19 具有对同一存储单元的两个引用的循环void copy(float a[], float b[], int n) { int i; for (i=0; i < n; i++) { a[i] = b[i]; /* S1 */ } }
由于变量 a 和 b 为参数,因此 a 和 b 可能指向重叠的内存区域,例如,如果 copy 的调用如下:
copy (x[10], x[11], 20);
在调用的例程中,copy 循环的两次连续迭代可能读/写数组 x 的同一元素。然而,如果例程 copy 的调用如下,则循环的 20 次迭代中不可能出现重叠:
copy (x[10], x[40], 20);
如果未提供有关如何调用例程的信息,编译器不可能正确地分析此情况。但是,Oracle Solaris Studio C 编译器提供标准 ISO C 的关键字扩展,以传达此类别名信息。有关更多信息,请参见受限指针。
别名问题的部分原因是:C 语言可以通过指针运算来定义数组引用及定义。为使编译器有效地自动并行化循环,所有采用数组布局的数据均必须使用 C 数组引用语法而不是指针进行引用。如果使用指针语法,编译器将无法确定循环的不同迭代之间的关系。因此,编译器将保守而不会并行化循环。
为使编译器有效地执行循环的并行执行任务,需要确定特定左值是否指定不同的存储区域。别名是其存储区域相同的左值。由于需要分析整个程序,因此确定对象的两个指针是否为别名是一个困难而费时的过程。考虑以下示例中的函数 vsq():
示例 3-20 带两个指针的循环void vsq(int n, double * a, double * b) { int i; for (i=0; i<n; i++) { b[i] = a[i] * a[i]; } }
如果编译器已确定指针 a 和 b 访问不同的对象,则可以并行化循环的不同迭代的执行。如果通过指针 a 和 b 访问的对象存在重叠,则编译器以并行方式执行循环将会不安全。在编译时,编译器无法通过简单地分析函数 vsq() 来确定 a 和 b 访问的对象是否重叠。编译器可能需要分析整个程序才能获取此信息。
限定指针用来指定那些指定不同对象的指针,以便编译器可以执行指针别名分析。以下示例说明了函数参数声明为限定指针的函数 vsq():
void vsq(int n, double * restrict a, double * restrict b)
指针 a 和 b 声明为限定指针,因此编译器知道 a 和 b 指向不同的存储区域。有了此别名信息,编译器就能够并行化循环。
关键字 restrict 是一个类型限定符,与 volatile 类似,但它仅限定指针类型。 在某些情况下,您可能不希望更改源代码。可以使用以下命令行选项指定将返回赋值指针函数参数视为限定指针:
-xrestrict=[func1,…,funcn]
如果指定函数列表,则指定的函数中的指针参数将被视为限定的;否则,整个 C 文件中的所有指针参数均被视为限定的。例如,-xrestrict=vsq 限定前一个有关键字 restrict 的函数 vsq() 示例中给定的指针 a 和 b。
正确使用 restrict 至关重要。如果指针被限定为限定指针而指向不同的对象,编译器会错误地并行化循环而导致不确定的行为。例如,假定函数 vsq() 的指针 a 和 b 指向的对象重叠,使 b[i] 和 a[i+1] 是同一对象。如果 a 和 b 未声明为限定指针,循环将以串行方式执行。如果 a 和 b 被错误地限定为限定指针,编译器会并行化循环的执行,但这是不安全的,因为 b[i+1] 应在 b[i] 之后进行计算。