ここでは、ベクトル化と関連するループの並列化について説明します。
-parallel (Linux*) オプションまたは /Qparallel (Windows*) オプションを使用すると、インテル製マイクロプロセッサーおよび互換マイクロプロセッサーにおいて並列化が有効になります。実行ファイルでは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる可能性があります。また、並列化は、/arch や /Qx (Windows*) または -m や -x (Linux* および Mac OS* X) などの特定のオプションによる影響を受けます。
-vec (Linux*) または /Qvec (Windows*) オプションを使用すると、インテル製マイクロプロセッサーおよび互換マイクロプロセッサーにおいて、デフォルトの最適化レベルでベクトル化が有効になります。ベクトル化により呼び出されるライブラリー・ルーチンは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる可能性があります。また、ベクトル化は、/arch や /Qx (Windows*) または -m や -x (Linux* および Mac OS* X) などの特定のオプションによる影響を受けます。
-parallel と -x (Linux* および Mac OS* X) または /Qparallel と /Qx (Windows*) オプションを組み合わせると、同じコンパイルで、自動ループ並列化と自動ループベクトル化の両方が試みられます。
多くの場合、コンパイラーは、並列化には最外ループ、ベクトル化には最内ループを認識します。しかし、有効であると判断された場合、コンパイラーは、同じループに並列化とベクトル化を適用します。
「自動並列化のプログラミング」および「ベクトル化のプログラミングにおけるガイドライン」を参照してください。
まれに、ループ並列化 (自動または OpenMP* 宣言子のいずれかによって) が成功すると、コンパイラーにレポートされるベクトル化されなかったループのメッセージに影響することがあります。例えば、-vec-report2 (Linux* および Mac OS* X) または /Qvec-report2 (Windows*) オプションでは、ループのベクトル化が成功しなかったことが示されます。
整数ループの場合、128 ビットのインテル® ストリーミング SIMD 拡張命令 (インテル® SSE) とインテル® アドバンスト・ベクトル・エクステンション (インテル® AVX) は 32 ビット、16 ビット、8 ビット、および限定対応の 64 ビットの整数データ型を使用するほとんどの算術演算子と論理演算子に対して SIMD 命令を提供します。
整数丸め演算の最終的な精度が保持される場合は、ベクトル化が可能です。例えば、最後に格納された値が 16 ビット整数である場合には、32 ビットの右シフト演算子は 16 ビット・モードではベクトル化されません。また、インテル® SSE およびインテル® AVX 命令セットは完全に直交型ではない (例えば、バイトオペランドのシフトはサポートされていない) ため、実際にはすべての整数演算をベクトル化ができないので注意してください。
32 ビット単精度および 64 ビット倍精度の浮動小数点数を操作するループの場合、インテル® SSE は算術演算子 (加算 (+)、減算 (-)、乗算 (*)、除算 (/)) に対して SIMD 命令を提供します。
また、ストリーミング SIMD 拡張命令は、MIN、MAX という二項演算子、および SQRT という単項演算子に SIMD 命令を提供しています。これ以外の複数の算術演算子の SIMD バージョン (三角関数 SIN、COS、TAN など) は、インテル® コンパイラーに添付されているベクトル数値ランタイム・ライブラリー内のソフトウェアでサポートしています。
ベクトル化が可能であるためには、ループは次の条件を満たしていなければなりません。
可算ループ: ループのトリップカウントは、ランタイム時にループの入口で判明していなければなりません。ただし、コンパイル時に判明している必要はありません (つまり、トリップカウントは変数にできますが、ループの実行中は一定である必要があります)。これは、ループの出口はデータ依存してはならないことを意味します。
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 -vec-report2 two_exits.cpp
two_exits.cpp(4) (列 9): リマーク: ループはベクトル化されませんでした: 非標準のループはベクトル化候補ではありません。
直列型コードが含まれている: SIMD 命令はオリジナルループの複数の反復からのデータ要素で同じ演算を実行します。そのため、反復ごとに異なる制御フローを持つことはできません。つまり、分岐してはなりません。したがって、switch 文は使用できないことになります。ただし、if 文がマスクされた代入として実装できる場合は使用できます (通常は使用可能)。演算はすべてのデータ要素に対して実行されますが、結果はマスクが TRUE に評価された要素のみ格納されます。この点について、ベクトル化が可能な次の例を参照してください。
#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 -vec-report2 quad.cpp
quad5.cpp(5) (列 3): リマーク: ループがベクトル化されました。
入れ子の最内ループである: 唯一の例外は、アンロール、ループコラプス、交換などの優先されるほかの最適化フェーズの結果として、またはループの実体化により、オリジナルの外側のループが内側のループに変換された場合です。
関数の呼び出しがない: print 文でさえもループのベクトル化の妨げになります。通常、ベクトル化レポートには「非標準のループはベクトル化候補ではありません」と出力されます。主な 2 つの例外としては、算術組み込み関数とインライン展開可能な関数です。
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* および MacOS* 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): リマーク: ループがベクトル化されました。
ベクトル化可能な演算は、浮動小数点データと整数データとで異なります。
整数配列の演算
ループ本体の文には、算術演算または論理演算 (これも、通常は配列) を使用できます。算術演算は、加算、減算、ABS、MIN、および MAX に制限されます。論理演算には、ビット単位の AND、OR、および XOR の演算子を含んでいます。データ型は混在させられますが、効率性の低下につながる恐れがあります。
データ依存性とは、シリアルループに含まれている各演算の実行順序を制限する関係のことです。ベクトル化によって演算の実行順序が並び替えられるため、自動ベクトライザーでは任意のデータ依存性の解析を自由に使用できなければなりません。
データの依存関係によりベクトル化が妨げられる例を次に示します。この例に示す配列の各要素の値は、前の繰り返しで計算された前後の要素の値により決まります。
例 1: データ依存性を持つループ |
---|
subroutine dep(data, n) real :: data(n) integer :: i do i = 1, n-1 data(i) = data(i-1)*0.25 + data(i)*0.5 + data(i+1)*0.25 end do end subroutine dep |
上記の例に示すループは、ベクトル化できません。これは、現在の要素 DATA(I) への WRITE が直前の要素 DATA(I-1) の使用に依存しており、この要素が直前の反復時にすでに書き込まれ変更されているためです。このことは、次の例に示すように、配列のアクセスパターンの最初の 2 回の反復を見ればわかります。
例 1: データ依存性を持つループをベクトル化したもの |
---|
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) の値は、最初の反復時に書き込まれます。ベクトル化を行うためには、元のループのセマンティクスを変えることなく、対象となるすべての反復を並列に実行しなければなりません。
データ依存性の解析とは、2 つのメモリーアクセスの重なり合う条件を見つけることです。その条件は、1 つのプログラムの中で参照を 2 回行うと仮定した場合は、次の 2 つの事項によって規定されます。
参照するいくつかの変数が、メモリー内の同じ領域のエイリアスであるかどうか (つまり、互いに重複しているかどうか)
配列参照の場合は、添字同士の関連性
配列参照のデータ依存アナライザーは一連のテストとして構成され、時間とスペースコストに加えて性能においても段階的に強化していきます。
いずれかの次元で独立性が認められれば、それによって依存関係が排除できるため、最初は 1 次元ずつ単純なテストをいくつか実行します。宣言されている次元境界を超える恐れのある多次元配列参照は、テストを実施する前に、線形形式に変換できます。
簡単なテストとして、高速最大公約数 (GCD) テストや拡張限界テストなどを使用できます。GCD テストでは、ループ・インデックスの係数の GCD で定数項を均等に等分できない場合、データの独立性が証明されます。拡張限界テストでは、添字式において極値がオーバーラップする可能性があるかどうかをチェックします。
どの単純なテストでも独立性を証明できなかった場合は、最終的に Fourier-Motzkin 法の消去を用いた強力な階層型依存性解法を使用して、すべての次元におけるデータ依存性問題を解決します。
最適化に関する注意事項 |
---|
インテル® コンパイラー、関連ライブラリーおよび関連開発ツールには、インテル製マイクロプロセッサーおよび互換マイクロプロセッサーで利用可能な命令セット (SIMD 命令セットなど) 向けの最適化オプションが含まれているか、あるいはオプションを利用している可能性がありますが、両者では結果が異なります。また、インテル® コンパイラー用の特定のコンパイラー・オプション (インテル® マイクロアーキテクチャーに非固有のオプションを含む) は、インテル製マイクロプロセッサー向けに予約されています。これらのコンパイラー・オプションと関連する命令セットおよび特定のマイクロプロセッサーの詳細は、『インテル® コンパイラー・ユーザー・リファレンス・ガイド』の「コンパイラー・オプション」を参照してください。インテル® コンパイラー製品のライブラリー・ルーチンの多くは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーでより高度に最適化されます。インテル® コンパイラー製品のコンパイラーとライブラリーは、選択されたオプション、コード、およびその他の要因に基づいてインテル製マイクロプロセッサーおよび互換マイクロプロセッサー向けに最適化されますが、インテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる傾向にあります。 インテル® コンパイラー、関連ライブラリーおよび関連開発ツールは、互換マイクロプロセッサー向けには、インテル製マイクロプロセッサー向けと同等レベルの最適化が行われない可能性があります。これには、インテル® ストリーミング SIMD 拡張命令 2 (インテル® SSE2)、インテル® ストリーミング SIMD 拡張命令 3 (インテル® SSE3)、ストリーミング SIMD 拡張命令 3 補足命令 (SSSE3) 命令セットに関連する最適化およびその他の最適化が含まれます。インテルでは、インテル製ではないマイクロプロセッサーに対して、最適化の提供、機能、効果を保証していません。本製品のマイクロプロセッサー固有の最適化は、インテル製マイクロプロセッサーでの使用を目的としています。 インテルでは、インテル® コンパイラーおよびライブラリーがインテル製マイクロプロセッサーおよび互換マイクロプロセッサーにおいて、優れたパフォーマンスを引き出すのに役立つ選択肢であると信じておりますが、お客様の要件に最適なコンパイラーを選択いただくよう、他のコンパイラーの評価を行うことを推奨しています。インテルでは、あらゆるコンパイラーやライブラリーで優れたパフォーマンスが引き出され、お客様のビジネスの成功のお役に立ちたいと願っております。お気づきの点がございましたら、お知らせください。 改訂 #20110307 |
© 1996-2011 Intel Corporation. 無断での引用、転載を禁じます。