インテル® C++ コンパイラー 18.0 デベロッパー・ガイドおよびリファレンス

インテル® グラフィックス・テクノロジーでのオフロードの開始

このトピックは、インテル® グラフィックス・テクノロジーをターゲットとする場合にのみ適用されます。

インテル® Cilk™ Plus は、インテル® C++ コンパイラー 18.0 では非推奨の古い機能です。プロセッサー・グラフィックスへオフロードする代替手段は、将来のリリースで提供される予定です。詳細は、「インテル® Cilk™ Plus の代わりに OpenMP* またはインテル® TBB を使用するためのアプリケーションの移行」を参照してください。

#pragma offload target(gfx) の後の _Cilk_for ループや _Cilk_for ループの入れ子構造内部のコード、および #pragma offload target(gfx)#pragma offload target(gfx_kernel) が指定された関数コードは、ターゲットと CPU 用にコンパイルされます。target 属性に加えて、関数コードでは __declspec(target(gfx)) (Windows* および Linux*) または __attribute__((target(gfx))) (Linux* のみ) を使用して vector 属性を指定することもできます。target(gfx_kernel) を使用すると、ホストとターゲット用のバージョンが生成されますが、ターゲット用のバージョンをオフロード領域から呼び出すことはできません。代わりに、引数として非同期オフロード API に渡す必要があります。詳しくは、「非同期オフロード」で説明します。

#pragma offload target(gfx) は、並列ループの前、完全入れ子並列ループの前、またはインテル® Cilk™ Plus の配列表記文の前でのみ指定できます。並列ループは、_Cilk_for ループとして明記されていなければなりません。

インテル® グラフィックス・テクノロジー向けにプログラムしている場合、#pragma offload に次の節を指定できます。

#pragma offload_wait target(gfx) wait (address_expression) は、オフロードブロックが続かないことを除き #pragma offload target(gfx) wait(address_expression) と同じ効果があります。

#pragma offload_transfer は、計算をオフロードしなくてもホストとターゲット間のデータ転送を可能にします。このプラグマは、#pragma offload と同じ節をサポートします。signal 節と wait 節を使用して、非同期データ転送を行うことができます。

pin 節は、ターゲットがアクセス可能なメモリーとの間でデータをコピーする代わりに、ホストとターゲット間で同じ物理メモリー領域を共有するため、はるかに高速です。このため、pin 節を使用することで、オフロードのコストは大幅に減ります。O(N2)) のように比較的小さなデータサイズの大量のワークを実行するカーネルでは、この最適化は重要ではありません。

ただし、OS は共有されるメモリーページをロックし、スワップできないようにするため、共有変数を多数使用するとシステム全体のパフォーマンスが低下する恐れがあります。

デフォルトでは、コンパイラーはホスト CPU とターゲットの両方で実行するアプリケーションを生成しますが、[Q]offload コンパイラー・オプションの否定形を使用して、同じソースコードから CPU のみで実行するアプリケーションを生成することもできます。

ターゲットへオフロードする例

unsigned parArrayRHist[256][256],
     parArrayGHist[256][256], parArrayBHist[256][256];

#pragma offload target(gfx) if (do_offload) \
     pin(inputImage: length(imageSize)) \
     out(parArrayRHist, parArrayGHist, parArrayBHist)

     __Cilk_for (int ichunk = 0; ichunk < chunkCount; ichunk++){
          …
     }

上記の例では、CPU コードが生成され、ランタイムは次の処理を行います。

完全な入れ子構造の _Cilk_for ループを使用してオフロードする例

float (* A)[k] = (float (*)[k])matA;
float (* B)[n] = (float (*)[n])matB;
float (* C)[n] = (float (*)[n])matC;

#pragma offload target(gfx) if (do_offload) \
     pin(A: length(m*k)), pin(B: length(k*n)), pin(C: length(m*n))

     __Cilk_for (int r = 0; r < m; r += TILE_m) {
          __Cilk_for (int c = 0; c < n; c += TILE_n) {
               …
          }
     }

上の例では、次のことが言えます。

ターゲットへ非同期オフロードする例

この例は、in1 配列の初期化をプロセッサー・グラフィックスへオフロードし、並行してホストで in2 を初期化します。そして、in1in2 を使用してホストで out の計算を実行します。

in1 の初期化が完了するのを待機するため、out の計算の前に omp taskwait プラグマを指定しています。

OpenMP* 構文を利用してプロセッサー・グラフィックスへオフロードするため、コンパイル時に、/Qopenmp /Qopenmp-offload=gfx (Windows*) または -qopenmp -qopenmp-offload=gfx (Linux*) オプションを指定する必要があります。


    int* in1 = (int*)malloc(SIZE * sizeof(int));
    int* in2 = (int*)malloc(SIZE * sizeof(int));
    int* out = (int*)malloc(SIZE * sizeof(int));

#pragma omp target map(tofrom: in1[0:SIZE]) nowait
#pragma omp parallel for
    for (int i = 0; i < SIZE; i++) {
        in1[i] = 1;
    }

#pragma omp parallel for
    for (int i = 0; i < SIZE; i++) {
        in2[i] = i;
    }

#pragma omp taskwait

#pragma omp parallel for
    for (int i = 0; i < SIZE; i++) {
        out[i] = in1[i] + in2[i];
    }

この例は、上記の OpenMP* 構文とほぼ同じですが、#pragma offload 構文を使用して記述されています。in1 の初期化が完了するのを待機するため、out の計算の前に offload_wait プラグマを指定しています。

    int* in1 = (int*)malloc(SIZE * sizeof(int));
    int* in2 = (int*)malloc(SIZE * sizeof(int));
    int* out = (int*)malloc(SIZE * sizeof(int));

#pragma offload target(gfx) pin(in1: length(SIZE)) signal(in1)
    _Cilk_for (int i = 0; i < SIZE; i++) {
        in1[i] = SIZE - i;
    }

    _Cilk_for (int i = 0; i < SIZE; i++) {
        in2[i] = i;
    }

#pragma offload_wait target(gfx) wait(in1)

    _Cilk_for (int i = 0; i < SIZE; i++) {
        out[i] = in1[i] + in2[i];
    }

関連情報