ISO C の別名を使用すると、ループを並列化できなくなることがあります。別名とは、2 個の参照が記憶領域の同じ位置を参照する可能性のある場合に発生します。次の例を考えてみましょう。
使用例 3-19 同じメモリー位置への 2 つの参照を持つループ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 ループの後続の 2 回の繰り返しが、配列 x の同じ要素を読み書きしている可能性があります。しかし、ルーチン copy が次のように呼び出された場合には、ループの 20 回の繰り返しのいずれかで重なり合う可能性がなくなります。
copy (x[10], x[40], 20);
ルーチンの呼び出し方法に関する情報なしで、コンパイラはこの状況を正しく解析できません。ただし、Oracle Solaris Studio の C コンパイラは、この種類の別名情報を伝えるために、ISO C 規格に対するキーワード拡張を提供しています。詳細については、制限付きポインタを参照してください。
別名の問題の一因は、配列参照とポインタ計算演算を定義できる C 言語の性質にあります。コンパイラが効率的にループを自動並列化できるようにするためには、配列として配置されているすべてのデータを、ポインタではなく C の配列参照の構文を使用して参照する必要があります。ポインタ構文が使用されると、コンパイラはループの異なる繰り返し間でのデータの関係を解析できなくなります。そのため、コンパイラは用心深くなり、ループを並列化しません。
コンパイラが効率的にループの並列化を実行するためには、特定の左辺値が記憶領域の別々の領域を指定しているかどうかを判断する必要があります。別名とは、記憶領域の決まった位置を示していない左辺値のことです。オブジェクトへの 2 個のポインタが別名であるかどうかを判断することは、困難で非常に時間がかかります。プログラム全体の解析が必要になることがあるためです。次の例の関数 vsq() を考えます。
使用例 3-20 2 つのポインタを持つループ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 のような型修飾子であり、ポインタ型のみを修飾します。ソースコードを変更しない場合があります。その場合、次のコマンド行オプションを使用して、ポインタ型の値をとる関数の引数を restrict ポインタとして扱うように指定できます。
-xrestrict=[func1,…,funcn]
関数リストが指定されている場合、指定された関数内のポインタパラメータは制限付きとして扱われます。指定されていない場合は、C ファイル全体のすべてのポインタパラメータが制限付きとして扱われます。たとえば、-xrestrict=vsq を使用すると、前述の関数 vsq() についての最初の例では、ポインタ a および b がキーワード restrict によって修飾されます。
restrict を正しく使用することはとても重要です。区別できないオブジェクトを指しているポインタを制限付きポインタにしてしまうと、ループを正しく並列化することができなくなり、不定な動作をすることになります。たとえば、関数 vsq() のポインタ a および b が重なりあっているオブジェクトを指している場合には、b[i] と a[i+1] などが同じオブジェクトである可能性があります。このとき a および b が制限付きポインタとして宣言されていなければ、ループは順次実行されます。a および b が間違って制限付きであると宣言されていれば、コンパイラはループを並列実行するようになりますが、この場合 b[i+1] の結果は b[i] を計算したあとでなければ得られないので、安全に実行することはできません。