この節では、ベクトル・プログラミングでよく起こる問題について簡単な例をいくつか挙げて説明します。
下の例に示したループはベクトルコピーを行うものですが、これはベクトル化されます。その理由は、コンパイラがdest[i]とsrc[i]との区別が付くのを証明できるからです。
ベクトル化可能なコピー操作(区別が付くのを証明されないため) |
---|
void vec_copy(float *dest, float *src, int len) { int i; for(i=0; i<len; i++;) { dest[i]=src[i]; } } |
下の例に示したrestrictキーワードは、各ポインタが別々のオブジェクトを参照しているのを示します。したがって、マルチ・バージョン・コードを生成しなくてもベクトル化できます。
ベクトル化できるだけの違いがあることを証明するために restrict を使用する |
---|
void vec_copy(float *restrict dest, float *restrict src, int len) { int i; for(i=0; i<len; i++) { dest[i]=src[i]; } } |
16バイト以上のデータ構造体または配列は、その基底アドレスが16の倍数になる方法で各構造体や配列要素の先頭をアライメントしてください。
下の図は、アライメントの合っていないデータが原因でデータ・キャッシュ・ユニット( DCU )を分割した場合の影響を示したものです。アライメントの合っていないデータをロードすると16バイト境界にまたがるため、メモリアクセスが1回余分に発生し、その結果、6〜12サイクルのストールが発生します。データのアライメントの合っているのがわかっている場合や、アライメントされているものとして指定した場合は、このストールを避けられます。
例えば、要素a[0]とb[0]が16バイト境界にアライメントされているのがわかっている場合は、アライメント・オプション(#pragma vector aligned)をオンにすると、次のループはベクトル化できます。
ポインタのアライメントが確認済みの場合 |
---|
float *a, *b; int i;
for(int i=0; i<10; i++) { a[i]=b[i]; } |
ベクトル化すると、ループはこのように実行します。
ベクトル反復処理であるa[0:3] = b[0:3]とa[4:7] = b[4:7]は、要素a[0]とb[0](同様にa[4]とb[4])が16バイトでアライメントしている場合には、アライメントの合った移動命令を使用して両方とも実装できます。
注意
不正なアライメント・オプションでベクトライザを指定すると、コンパイラの動作は予測がつかなくなります。特に、アライメントの合っていないデータを、アライメントの合っている移動命令で処理すると、不正命令例外が発生します。
下の例は、アライメントの合っていないメモリ命令でしかベクトル化しないループを示したものです。コンパイラはこのローカル配列をアライメントできますが、コンパイル時には lb の値がわからないため、アライメントが正しいかどうかの判断はできません。
コンパイル時に変数の値がわからないためにアライメントされないループ |
---|
void f(int lb) { float z2[N], a2[N], y2[N], x2; for(i=lb; i<N; i++) { a2[i]=a2[i]*x2+y2[i]; } } |
lbが4の倍数であることがわかっているときは、下の例に示したように#pragma vector alignedを使用してループをアライメントできます。
変数の値が4の倍数であるという前提でアライメントを行う |
---|
void f(int lb) { float z2[N], a2[N], y2[N], x2; assert(lb%4==0);
#pragma vector aligned
for(i=lb; i<N; i++) { a2[i]=a2[i]*x2+y2[i]; } } |