インテル® C++ コンパイラー 16.0 ユーザー・リファレンス・ガイド
ここでは、自動ベクトル化とループの相互作用について詳しく説明します。
[Q]parallel と [Q]x オプションを組み合わせると、1 回のコンパイルで、自動並列化と自動ループベクトル化の両方を試みることができます。
このオプションを使用すると、互換マイクロプロセッサーおよびインテル製マイクロプロセッサーの両方で並列化が有効になります。実行ファイルは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる可能性があります。また、並列化は、/arch (Windows*)、-m (Linux* および OS X*)、[Q]x などの特定のオプションによる影響を受けます。
このオプションを使用すると、インテル製マイクロプロセッサーおよび互換マイクロプロセッサーの両方で、デフォルトの最適化レベルのベクトル化が有効になります。ベクトル化により呼び出されるライブラリー・ルーチンは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる可能性があります。また、ベクトル化は、/arch (Windows*)、-m (Linux* および OS X*)、[Q]x などの特定のオプションによる影響を受けます。
多くの場合、コンパイラーは、並列化には最外ループ、ベクトル化には最内ループを認識します。しかし、有効であると判断された場合、コンパイラーは同じループに並列化とベクトル化を適用します。
「自動並列化のプログラミング」および「ベクトル化のプログラミングにおけるガイドライン」を参照してください。
まれに、ループ並列化 (自動または OpenMP* 宣言子のいずれかによって) が成功すると、コンパイラーにレポートされるベクトル化されなかったループのメッセージに影響することがあります。例えば、Qopt-report:2Qopt-report-phase:vec (Windows*) または qopt-report=2qopt-report-phase=vec (Linux* および OS X*) オプションでは、ループのベクトル化が成功しなかったことが示されます。
整数ループの場合、128 ビットのインテル® ストリーミング SIMD 拡張命令 (インテル® SSE) とインテル® アドバンスト・ベクトル・エクステンション (インテル® AVX) は 32 ビット、16 ビット、8 ビット、および限定対応の 64 ビットの整数データ型を使用するほとんどの算術演算子と論理演算子に対して SIMD 命令を提供します。
整数丸め演算の最終的な精度が保持される場合は、ベクトル化が可能です。例えば、最後に格納された値が 16 ビット整数である場合には、32 ビットの右シフト演算子は 16 ビット・モードではベクトル化されません。また、インテル® SSE およびインテル® AVX 命令セットは完全に直交型ではない (例えば、バイト・オペランドのシフトはサポートされていない) ため、実際にはすべての整数演算をベクトル化ができないので注意してください。
32 ビット単精度および 64 ビット倍精度の浮動小数点数を操作するループの場合、インテル® SSE は次の算術演算子に対して SIMD 命令を提供します。
加算 (+)
減算 (-)
乗算 (*)
除算 (/)
また、インテル® SSE は、MIN、MAX という二項演算子、および SQRT という単項演算子に SIMD 命令を提供しています。これ以外の複数の算術演算子の SIMD バージョン (三角関数 SIN、COS、TAN など) は、インテル® コンパイラーで提供されるベクトル数値ランタイム・ライブラリー内のソフトウェアでサポートしています。
ベクトル化が可能であるためには、ループは次の条件を満たしていなければなりません。
可算ループ: ループの反復回数は、ランタイム時にループの入口で判明していなければなりません。ただし、コンパイル時に判明している必要はありません (つまり、反復回数は変数にできますが、ループの実行中は一定である必要があります)。これは、ループの出口はデータに依存してはならないことを意味します。
1 つの入口と 1 つの出口: ループは可算ループでなければならないことを意味します。データ依存性を持つ出口があるために、ベクトル化できない次のループの例について考えてみてください。
例 1: ベクトル化が不可能なループ |
---|
void no_vec(float a[], float b[], float c[]){ int i = 0.; while (i < 100) { a[i] = b[i] * c[i]; // これはデータ依存の出口条件: if (a[i] < 0.0) break; ++i; } } |
> icc -c -O2 -qopt-report=2 -qopt-report-phase=vec two_exits.cpp two_exits.cpp(4) (列 9): リマーク: ループはベクトル化されませんでした: 非標準のループはベクトル化候補ではありません。 |
直列型コード: SIMD 命令は、オリジナルループと同じ演算を実行するため、反復ごとに異なる制御フローを持つことはできません。つまり、分岐してはなりません。switch 文も使用できません。ただし、if 文はマスク付きの代入として実装できる場合のみ使用できます (通常は使用可能)。演算はすべてのデータ要素に対して実行されますが、結果はマスクが TRUE に評価された要素のみ格納されます。この点について、ベクトル化が可能な次の例を参照してください。
例 2: ベクトル化が可能なループの評価 |
---|
#include <math.h> void quad(int length, float *a, float *b, float *c, float *restrict x1, float *restrict x2) { for (int i=0; i<length; i++) { float s = b[i]*b[i] - 4*a[i]*c[i]; if ( s >= 0 ) { s = sqrt(s) ; x2[i] = (-b[i]+s)/(2.*a[i]); x1[i] = (-b[i]-s)/(2.*a[i]); } else { x2[i] = 0.; x1[i] = 0.; } } } |
> icc -c -restrict -qopt-report=2 -qopt-report-phase=vec quad.cpp quad5.cpp(5) (列 3): リマーク: ループがベクトル化されました。 |
入れ子構造の最内ループである: 通常ベクトル化は最内ループに対して行われます。唯一の例外は、ループアンロール、ループ結合、ループ交換など、優先されるその他の最適化フェーズの結果として、オリジナルの外側のループが内側のループに変換される場合です。
関数の呼び出しがない: print 文でさえもループのベクトル化の妨げになります。通常、ベクトル化レポートには「非標準のループはベクトル化候補ではありません」と出力されます。関数呼び出しを含むループはベクトル化されません。例外として、算術組込み関数とインライン展開可能な関数です。
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:2Qopt-report-phase:ipo (Windows*) または qopt-report=2qopt-report-phase=ipo (Linux*) オプションを指定して取得できます。
例 3: ベクトル化が可能なループのインライン展開 |
---|
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 -qopt-report=2 -qopt-report-phase=vec trap_integ.c trap_int.c(16) ループがベクトル化されました。 |
ベクトル化可能な演算は、浮動小数点データと整数データとで異なります。
整数配列の演算
ループ本体内の文では、char、unsigned char、short、unsigned short、int、unsigned int 型を使用できます。sqrt や fabs といった関数を呼び出すこともできます。使用可能な算術演算は、加算、減算、ビット単位 AND、ビット単位 OR、ビット単位 XOR、除算 (ランタイム・ライブラリー・コール経由)、乗算、min、および max だけです。データ型は混在させられますが、効率性の低下につながる恐れがあります。例えば、乗算演算子、シフト演算子、単項演算子は混在させられます。
上記の浮動小数点演算と整数演算以外の文は使用できません。特に、特殊なデータ型である __m64、__m128、__m256 はベクトル化できないので注意してください。ループ本体に関数呼び出しを含めることはできません。インテル® SSE の組込み関数 (例: _mm_add_ps) またはインテル® AVX 組込み関数 (例: _mm256_add_ps) は使用できません。
インテル® コンパイラーは、互換マイクロプロセッサー向けには、インテル製マイクロプロセッサー向けと同等レベルの最適化が行われない可能性があります。これには、インテル® ストリーミング SIMD 拡張命令 2 (インテル® SSE2)、インテル® ストリーミング SIMD 拡張命令 3 (インテル® SSE3)、ストリーミング SIMD 拡張命令 3 補足命令 (SSSE3) 命令セットに関連する最適化およびその他の最適化が含まれます。インテルでは、インテル製ではないマイクロプロセッサーに対して、最適化の提供、機能、効果を保証していません。本製品のマイクロプロセッサー固有の最適化は、インテル製マイクロプロセッサーでの使用を目的としています。インテル® マイクロアーキテクチャーに非固有の特定の最適化は、インテル製マイクロプロセッサー向けに予約されています。この注意事項の適用対象である特定の命令セットの詳細は、該当する製品のユーザー・リファレンス・ガイドを参照してください。 改訂 #20110804 |