ベクトル化とループ

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

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

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

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

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

整数ループの場合、64 ビットの MMX(R) テクノロジーおよび 128 ビットのストリーミング SIMD 拡張命令 (SSE) は 32 ビット、16 ビット、および 8 ビットの整数データ型を使用するほとんどの算術演算子と論理演算子に対して SIMD 命令を提供します。

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

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

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

ループ本体内の文

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

整数配列の演算

ループ本体内の文には、charunsigned charshortunsigned shortint、および unsigned int という各型を使用できます。sqrtfabs といった関数を呼び出せます。算術演算に使用できるのは、加算、減算、ビット単位 AND、ビット単位 OR、ビット単位 XOR、除算 (16 ビットのみ)、乗算 (16 ビットのみ)、最小値、および最大値だけです。データ型を複数混在させられるのは、変換しても精度が失われない場合だけです。例えば、乗算演算子、シフト演算子、単項演算子は混在させられます。

その他の演算

上記の浮動小数点演算と整数演算以外の文は使用できません。特に、特殊なデータ型である __m64__m128 はベクトル化できないので注意してください。ループ本体に関数呼び出しを含めることはできません。ストリーミング SIMD 拡張命令の組み込み関数 (_mm_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: データ依存性を持つループをベクトル化したもの

for(i=0; i<100; i++)

a[i]=b[i];

has access pattern

read b[0]

write a[0]

read b[1]

write a[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 つの事項によって規定されます。

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

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

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

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