コンパイラが効率よくループを並列化できるようにするには、左辺値が記憶領域の特定の領域を示している必要があります。別名とは、記憶領域の決まった位置を示していない左辺値のことです。オブジェクトへの 2 個のポインタが別名であるかどうかを判断することは困難です。これを判断するにはプログラム全体を解析する必要があるため、非常に時間がかかります。次の関数 vsq() を考えてみましょう。
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 によるオブジェクトのアクセスが重なりあっているかどうかを知ることはできません。この情報を得るには、コンパイラはプログラム全体を解析する必要があります。
restrict ポインタを使ってオブジェクトを明確に区別すると、コンパイラによるポインタ別名の解析が実行可能になります。次に、vsq() の関数パラメータを restrict ポインタとして宣言した例を示します。
void vsq(int n, double * restrict a, double * restrict b) |
ポインタ a および b が restrict ポインタとして宣言されているので、a および b で示された記憶領域が区別されていることがわかります。この別名情報によって、コンパイラはループの並列化を実行できます。
キーワード restrict は volatile に似た型修飾子で、ポインタ型に対して有効です。なお、restrict は -Xs モードでコンパイルする場合を除き、-xc99=all を使用する場合に有効なキーワードです。ソースコードを変更しない場合があります。次のコマンド行オプションで、関数の引数を値とするポインタを restrict ポインタとして指定できます。
-xrestrict=[func1,…,funcn] |
関数リストが指定されている場合には、指定された関数内のポインタパラメータは restrict として扱われます。指定されていない場合には、C のソースファイル全体のすべてのポインタ引数が restrict として扱われます。たとえば -xrestrict=vsq を使用すると、前述の関数 vsq() についての最初の例では、ポインタ a および b がキーワード restrict によって修飾されます。
restrict を正しく使用することはとても重要です。区別できないオブジェクトを指しているポインタを restrict ポインタにしてしまうと、ループを正しく並列化できなくなり、不定な動作をすることになります。たとえば、関数 vsq() のポインタ a および b が重なりあっているオブジェクトを指している場合には、b[i] と a[i+1] などが同じオブジェクトである可能性があります。このとき a および b が restrict ポインタとして宣言されていなければ、ループは順次実行されます。a および b が間違って restrict と宣言されていれば、コンパイラはループを並列実行するようになりますが、この場合 b[i+1] の結果は b[i] を計算したあとでなければ得られないので、安全に実行できません。