インテル® C++ コンパイラー XE 13.1 ユーザー・リファレンス・ガイド

ベクトル化とループ

ここでは、ベクトル化と関連するループの並列化について説明します。

-parallel (Linux*) オプションまたは /Qparallel (Windows*) オプションを使用すると、インテル製マイクロプロセッサーおよび互換マイクロプロセッサーにおいて並列化が有効になります。 実行ファイルでは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる可能性があります。また、並列化は、/arch/Qx (Windows*) または -m-x (Linux* および OS X*) などの特定のオプションによる影響を受けます。

-vec (Linux*) または /Qvec (Windows*) オプションを使用すると、インテル製マイクロプロセッサーおよび互換マイクロプロセッサーにおいて、デフォルトの最適化レベルでベクトル化が有効になります。 ベクトル化により呼び出されるライブラリー・ルーチンは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる可能性があります。また、ベクトル化は、/arch/Qx (Windows*) または -m-x (Linux* および OS X*) などの特定のオプションによる影響を受けます。

ループの並列化との相互作用

-parallel (Linux* および OS X*) または /Qparallel (Windows*) と -x (Linux*) または /Qx (Windows*) オプションを組み合わせると、同じコンパイルで、自動ループ並列化と自動ループベクトル化の両方が試みられます。

多くの場合、コンパイラーは、並列化には最外ループ、ベクトル化には最内ループを認識します。しかし、有効であると判断された場合、コンパイラーは、同じループに並列化とベクトル化を適用します。

「自動並列化のプログラミング」および「ベクトル化のプログラミングにおけるガイドライン」を参照してください。

まれに、ループ並列化 (自動または OpenMP* 宣言子のいずれかによって) が成功すると、コンパイラーにレポートされるベクトル化されなかったループのメッセージに影響することがあります。例えば、-vec-report2 (Linux* および OS X*) または /Qvec-report2 (Windows*) オプションでは、ループのベクトル化が成功しなかったことが示されます。

ベクトル化されるループの種類

整数ループの場合、128 ビットのインテル® ストリーミング SIMD 拡張命令 (インテル® SSE) とインテル® アドバンスト・ベクトル・エクステンション (インテル® AVX) は 32 ビット、16 ビット、8 ビット、および限定対応の 64 ビットの整数データ型を使用するほとんどの算術演算子と論理演算子に対して SIMD 命令を提供します。

整数丸め演算の最終的な精度が保持される場合は、ベクトル化が可能です。例えば、最後に格納された値が 16 ビット整数である場合には、32 ビットの右シフト演算子は 16 ビット・モードではベクトル化されません。また、インテル® SSE およびインテル® AVX 命令セットは完全に直交型ではない (例えば、バイトオペランドのシフトはサポートされていない) ため、実際にはすべての整数演算をベクトル化ができないので注意してください。

32 ビット単精度および 64 ビット倍精度の浮動小数点数を操作するループの場合、インテル® SSE は算術演算子 (加算 (+)、減算 (-)、乗算 (*)、除算 (/)) に対して SIMD 命令を提供します。

また、ストリーミング SIMD 拡張命令は、MINMAX という二項演算子、および SQRT という単項演算子に SIMD 命令を提供しています。 これ以外の複数の算術演算子の SIMD バージョン (三角関数 SINCOSTAN など) は、インテル® コンパイラーに添付されているベクトル数値ランタイム・ライブラリー内のソフトウェアでサポートしています。

ベクトル化が可能であるためには、ループは次の条件を満たしていなければなりません。

sin()log()fmax() などの算術組込み関数は、コンパイラーのランタイム・ライブラリーにベクトル化されたバージョン (ベクター・マス・ライブラリー) が含まれているため、利用できます。 このような関数を次にリストします。多くは float と double の両方があります。

acos ceil fabs round
acosh cos floor sin
asin cosh fmax sinh
asinh erf fmin sqrt
atan erfc log tan
atan2 erfinv log10 tanh
atanh exp log2 trunc
cbrt exp2 pow  

次の例のループは、sqrtf() がベクトル化可能で、func() がインライン展開されるため、ベクトル化できます。 デフォルトの最適化により、同じソースファイル内のすべての関数のインライン展開が有効になります。インライン展開レポートは /Qopt-report-phase ipo_inl (Windows*) または -opt-report-phase ipo_inl (Linux* および OS X*) オプションを指定して取得できます。

float func(float x, float y, float xp, float yp) {
  float denom;
  denom = (x-xp)*(x-xp) + (y-yp)*(y-yp);
  denom = 1./sqrtf(denom);
  return denom;
}
float trap_int(float y, float x0, float xn, int nx, float xp, float yp) {
  float x, h, sumx;
  int i;
  h = (xn-x0) / nx;
  sumx = 0.5*( func(x0,y,xp,yp) + func(xn,y,xp,yp) );
  for (i=1;i<nx;i++) {
    x = x0 + i*h;
    sumx = sumx + func(x,y,xp,yp);
  }
  sumx = sumx * h;
  return sumx; 
}
// コマンドライン 
> icc -c -vec-report2 trap_integ.c
trap_int.c(16) (列 3): リマーク: ループがベクトル化されました。

ループ本体内の文

ベクトル化可能な演算は、浮動小数点データと整数データとで異なります。

整数配列の演算

ループ本体内の文では、charunsigned charshortunsigned shortintunsigned int 型を使用できます。 sqrtfabs といった関数を呼び出すこともできます。 使用可能な算術演算は、加算、減算、ビット単位 AND、ビット単位 OR、ビット単位 XOR、除算 (ランタイム・ライブラリー・コール経由)、乗算、min、および max だけです。 データ型は混在させられますが、効率性の低下につながる恐れがあります。例えば、乗算演算子、シフト演算子、単項演算子は混在させられます。

その他の演算

上記の浮動小数点演算と整数演算以外の文は使用できません。特に、特殊なデータ型である __m64__m128__m256 はベクトル化できないので注意してください。 ループ本体に関数呼び出しを含めることはできません。インテル® ストリーミング SIMD 拡張命令の組込み関数 (例: _mm_add_ps) またはインテル® AVX 組込み関数 (例: _mm256_add_ps) は使用できません。

データ依存性

データ依存性とは、シリアルループに含まれている各演算の実行順序を制限する関係のことです。ベクトル化によって演算の実行順序が並び替えられるため、自動ベクトライザーでは任意のデータ依存性の解析を自由に使用できなければなりません。

データの依存関係によりベクトル化が妨げられる例を次に示します。この例に示す配列の各要素の値は、前の繰り返しで計算された前後の要素の値により決まります。

例 1: データ依存性を持つループ

int i;
void dep(float *data){
  for (i=1; i<100; i++){
   data[i] = data[i-1]*0.25 + data[i]*0.5 + data[i+1]*0.25;
  }
}

上記の例に示すループは、ベクトル化できません。これは、現在の要素 DATA(I) への WRITE が直前の要素 DATA(I-1) の使用に依存しており、この要素が直前の反復時にすでに書き込まれ変更されているためです。 このことは、次の例に示すように、配列のアクセスパターンの最初の 2 回の反復を見れば分かります。

例 2: データ依存性を持つループをベクトル化したもの

i=1: READ data[0]
READ data[1]
READ data[2]
WRITE data[1]
i=2: READ data[1]
READ data[2]
READ data[3]
WRITE data[2]

このループが示す通常のシーケンスでは、2 回目の反復時に読み込まれる DATA(1) の値は、最初の反復時に書き込まれます。 ベクトル化を行うためには、元のループのセマンティクスを変えることなく、対象となるすべての反復を並列に実行しなければなりません。

例 3: データに依存しないループ

for(i=0; i<100; i++)
a[i]=b[i];
// アクセスパターンは次のとおり
read b[0]
write a[0]
read b[1]
write a[1]

データ依存性の解析

データ依存性の解析とは、2 つのメモリーアクセスの重なり合う条件を見つけることです。その条件は、1 つのプログラムの中で参照を 2 回行うと仮定した場合は、次の 2 つの事項によって規定されます。

配列参照のデータ依存アナライザーは一連のテストとして構成され、時間とスペースコストに加えて性能においても段階的に強化していきます。

いずれかの次元で独立性が認められれば、それによって依存関係が排除できるため、最初は 1 次元ずつ単純なテストをいくつか実行します。宣言されている次元境界を超える恐れのある多次元配列参照は、テストを実施する前に、線形形式に変換できます。

簡単なテストとして、高速最大公約数 (GCD) テストや拡張限界テストなどを使用できます。GCD テストでは、ループ・インデックスの係数の GCD で定数項を均等に等分できない場合、データの独立性が証明されます。拡張限界テストでは、添字式において極値がオーバーラップする可能性があるかどうかをチェックします。

どの単純なテストでも独立性を証明できなかった場合は、最終的に Fourier-Motzkin 法の消去を用いた強力な階層型依存性解法を使用して、すべての次元におけるデータ依存性問題を解決します。

最適化に関する注意事項

インテル® コンパイラーは、互換マイクロプロセッサー向けには、インテル製マイクロプロセッサー向けと同等レベルの最適化が行われない可能性があります。これには、インテル® ストリーミング SIMD 拡張命令 2 (インテル® SSE2)、インテル® ストリーミング SIMD 拡張命令 3 (インテル® SSE3)、ストリーミング SIMD 拡張命令 3 補足命令 (SSSE3) 命令セットに関連する最適化およびその他の最適化が含まれます。インテルでは、インテル製ではないマイクロプロセッサーに対して、最適化の提供、機能、効果を保証していません。本製品のマイクロプロセッサー固有の最適化は、インテル製マイクロプロセッサーでの使用を目的としています。インテル® マイクロアーキテクチャーに非固有の特定の最適化は、インテル製マイクロプロセッサー向けに予約されています。この注意事項の適用対象である特定の命令セットの詳細は、該当する製品のユーザー・リファレンス・ガイドを参照してください。

改訂 #20110804


このヘルプトピックについてのフィードバックを送信