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

SIMD 対応関数

SIMD 対応関数 (以前は要素関数と呼ばれていたもの) は、データ並列アルゴリズムを表現するための一般的な言語構造です。通常の C/C++ 関数として記述され、そのアルゴリズムはスカラー構文を使用して 1 つの要素に対する操作を指示します。1 つの要素に対する操作を行う場合は通常の C/C++ 関数として呼び出され、複数の要素に対する操作の場合はデータ並列コンテキストで呼び出されます。インテル® Cilk™ Plus では、データ並列コンテキストは配列として提供されます。

SIMD 対応関数を使用し、コンパイラー・オブジェクト・ファイルを以前のバージョンのコンパイラー (13.1 など) のオブジェクト・ファイルとリンクする場合、[Q]vecabi legacy コンパイラー・オプションを使用する必要があります。デフォルト値 (compat) は、インテル® Cilk™ Plus と OpenMP* 4.0 の両方の gcc ベクトル関数サポートと互換性があります。

SIMD 対応関数の動作

SIMD 対応関数がある場合、コンパイラーは一度の呼び出しで複数の引数に対して操作が行えるようにそのショートベクトル形式を生成します。このショート・ベクトル・バージョンは、CPU のベクトル命令セット・アーキテクチャー (ISA) を活用することで、通常の実装で個別に操作を行った場合と同じぐらい高速に複数の操作を行うことができます。さらに、cilk_for または pragma omp 構造から呼び出された場合、コンパイラーは SIMD 対応関数の異なるコピーを異なるスレッド (またはワーカー) に割り当てて同時に実行することがあります。その結果、データ並列操作は、マルチコアで利用可能な並列性とベクトル用の ISA で利用可能な並列性の両方を活用して CPU で実行されます。

並列ループ (cilk_for ループまたはベクトル化された自動並列化ループ) の内側でショートベクトル関数が呼び出されると、ベクトルレベルの並列化とスレッドレベルの並列化の両方を達成できます。

SIMD 対応関数の宣言

コンパイラーがショートベクトル関数を生成するようにコード中で指示する必要があります。

Windows*:

次のように、__declspec(vector (clauses)) 宣言を使用します。

__declspec(vector (clauses)) return_type simd_enabled_function_name(arguments)

Linux* および OS X*:

次のように、__attribute__((vector (clauses))) 宣言を使用します。

__attribute__((vector (clauses))) return_type simd_enabled_function_name(arguments)

vector 宣言の clauses には、次の値を使用することができます。

processor(cpuid)

cpuid には、次のいずれかの値を指定できます。

  • atom

  • mic

  • core_aes_pclmulqdq

  • core_i7_sse4_2

  • core_2_duo_sse4_1

  • core_2_duo_ssse3

  • core_2nd_gen_avx

  • core_3rd_gen_avx

  • pentium_4_sse3

  • pentium_4

  • pentium_m

vectorlength(n)

n はベクトル長です。2 のべき乗の整数値で、2、4、8、または 16 でなければなりません。

vectorlength 節は、各ルーチンの呼び出しで、スカラー関数を n 回実行するのと同じ分の計算を実行するようにコンパイラーに指示します。

linear(param1:step1 [, param2:step2]…)

説明:

  • param はスカラー変数です。

  • step はコンパイル時の正の整数定数式です。

    linear 節は、シリアル実行においてルーチンの連続した呼び出しで、param1 の値は step1 ずつ、param2step2 ずつというように増分されるようにコンパイラーに指示します。特定の変数に複数の step が指定されている場合は、コンパイル時にエラーが発生します。複数の linear 節は結合されます。

uniform(param [, param,]…)

param は、指定された関数の仮引数または C++ キーワード this です。

uniform 節は、パフォーマンスを最適化するために、指定された引数の値をすべての反復にブロードキャストするようにコンパイラーに指示します。複数の uniform 節は結合されます。

[no]mask

[no]mask 節は、ルーチンのマスク付きのベクトルバージョンを生成するようにコンパイラーに指示します。

既存の C/C++ 構文と関連するビルトイン関数 (後述の「__intel_simd_lane()」を参照) を使用して関数の中にこのコードを記述します。

並列コンテキストでの SIMD 対応関数の呼び出し

通常、SIMD 対応関数で仮パラメーターとしてスカラー引数が指定されている場合、その呼び出しは配列を提供します。この配列は、インテル® Cilk™ Plus の配列表記を使用して簡潔に提供することができます。また、_Cilk_for ループから SIMD 対応関数を呼び出すこともできます。

次の 2 つの構文は、コンパイラーに特殊なベクトル命令を発行させることで命令レベルの並列処理を実現します。

a[:] = ef_add(b[:],c[:]);    // 配列 a、b、c 全体に対して処理を行う

a[0:n:s] = ef_add(b[0:n:s],c[0:n:s]); // 配列表記構造で n (長さ) と s (ストライド) を指定する

データ並列コンテキストで SIMD 対応関数を呼び出し、複数のコアとプロセッサーを使用するには _Cilk_for を使用します。

_Cilk_for (j = 0; j < n; ++j) {
  a[j] = ef_add(b[j],c[j]);
}

_Cilk_for 呼び出し構文を使用するコードのみが利用可能なすべての並列性を使用できます。

通常の for ループからの SIMD 対応関数の呼び出しおよび配列表記構文を使用すると、各反復でショートベクトル関数が呼び出され、この呼び出しはマルチコアを活用することなくシリアルループで行われます。利用可能なすべての並列性を使用するには、インテル® Cilk™ Plus キーワード (cilk_for、cilk_spawn など) または OpenMP* の使用を考慮すべきです。

__intel_simd_lane() ビルトイン関数の使用

ベクトル化されたループから呼び出される場合、__intel_simd_lane() ビルトイン関数は、SIMD ベクトル内の現在の "レーン ID" を示す 0 ~ vectorlength - 1 の範囲の値を返します。ループがベクトル化されていない場合、__intel_simd_lane() は 0 を返します。__intel_simd_lane() は、明示的なベクトル・プログラミング構造の範囲外で呼び出さないでください。自動ベクトル化を妨げる可能性があります。また、そのような呼び出しは通常、0 ~ vectorlength-1 の範囲内の値ではなく 0 を返します。

__intel_simd_lane() の使用法を示す次の例について考えてみます。

void accumulate(float *a, float *b, float *c, d){
  *a+=sin(d);
  *b+=cos(d);
  *c+=log(d);
}

for (i=low; i<high; i++){
    accumulate(&suma, &sumb, &sumc, d[i]);
}

まず、__intel_simd_lane() を使用せずにインテル® Cilk™ Plus のベクトルコードに変換します。

#define VL 16
__declspec(vector(uniform(a,b,c), linear(i)))
void accumulate(float *a, float *b, float *c, d, i){
  a[i & (VL-1)]+=sin(d);
  b[i & (VL-1)]+=cos(d);
  c[i & (VL-1)]+=log(d);
}

float a[VL] = {0.0f};
float b[VL] = {0.0f};
float c[VL] = {0.0f};
#pragma omp simd for safe_veclen(VL)
for (i=low; i<high; i++){
    accumulate(a, b, c, d[i], i);
}
for(i=0;i<VL;i++){
    suma += a[i];
    sumb += b[i];
    sumc += c[i];
}

SIMD 対応関数 accumulate() の配列 A、B、C への参照で生じるギャザー/スキャッター形式のメモリーアドレス指定は、パフォーマンスを大幅に低下させ、変換した意味がなくなってしまいます。このペナルティーを回避するため、次のように __intel_simd_lane() ビルトイン関数を使用します。

__declspec(vector(uniform(a,b,c),aligned(a,b,c)))
void accumulate(float *a, float *b, float *c, d){
  // "ループ・インデックス"、VL は不要。a[__intel_simd_lane()]+=sin(d);
  b[__intel_simd_lane()]+=cos(d);
  c[__intel_simd_lane()]+=log(d);
}

#define VL 16 // 実際の SIMD コードではベクトル長 4 を使用している可能性がある
float a[VL] = {0.0f};
float b[VL] = {0.0f};
float c[VL] = {0.0f};
#pragma omp simd for safe_veclen(VL)
for (i=low; i<high; i++){
    // コンパイル時に low が 0 と判明している場合、“i & (VL-1)” は
    // 呼び出し元で __intel_simd_lane() の意図したとおりになる

    accumulate(a, b, c, d[i]);
}
for(i=0;i<VL;i++){
    suma += a[i];
    sumb += b[i];
    sumc += c[i];
}

__intel_simd_lane() を使用することで、accumulate() 内の配列参照をユニットストライドにできます。

制限事項

次の言語構造は SIMD 対応関数内で許可されていません。

関連情報