インテル® 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 に次の節を指定できます。
target(gfx) - ターゲットにオフロードされたコードセクションのヘテロジニアス実行に必須の節です。
if (condition) - 条件が true の場合、コードはターゲットでのみ実行されます。
in|out|inout|pin(variable_list: length(length_variable_in_elements))
in、out、inout - 変数は CPU とターゲットメモリー間でコピーされます。
pin - 変数は CPU とターゲット間で共有されます。
ポインターの length 節を含める必要があります。この節は、ポインターで参照されている型の要素で、ターゲットとコピーまたは共有するデータのサイズを示します。配列へのポインターでは、サイズは参照されている配列の要素で指定されます。
signal(address_expression) - 非同期オフロードを指定します。オフロードを開始した CPU スレッドは、オフロードの完了を待たずに実行を継続します、address_expression はオフロードタスクを識別し、CPU スレッドが #pragma offload target(gfx) wait(address_expression) によりプログラムの任意の場所でこのオフロードタスクの完了を待機できるようにします。
wait(address_expression) - wait を呼び出した CPU スレッドは、それ以前の #pragma offload target(gfx) signal 構造で指定されたアドレスに関連付けられたすべてのオフロードタスクが完了するまで待機し、その後プラグマに続くオフロードブロックを実行します。
#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 コードが生成され、ランタイムは次の処理を行います。
ターゲットがシステムで利用できるかどうかを判断します。
ターゲットが利用できない場合、または do_offload の評価に失敗した場合、for ループを CPU で実行します。
それ以外の場合は、次の処理を行います。
ポインター inputImage で参照される imageSize * sizeof(inputImage[0]) バイトを固定して、ターゲットメモリーとの間でデータをコピーせずに、そのメモリーをターゲットと共有します。
parArrayRHist、parArrayGHist、および parArrayBHist のターゲットメモリー領域を作成します。
for ループの反復空間を N 個のチャンクに分割します (N は chunkCount 以下)。N の特定の値の選択はオフロードランタイムによって行われます。ドキュメントで説明されているように、境界やストライドのような反復空間構成や、環境変数で制御できる最大値のような要因に依存します。
それぞれ独自の反復空間チャンクを割り当てた N 個のターゲットスレッドでタスクを作成します。
ターゲットで実行するタスクをキューに入れます。
ターゲットのタスクの実行が完了するのを待ちます。
ターゲットメモリーから CPU メモリーに parArrayRHist、parArrayGHist、および parArrayBHist をコピーして、結果が直ちにすべての CPU スレッドで認識されるようにします。
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) {
…
}
}
上の例では、次のことが言えます。
完全な入れ子構造の __Cilk_for ループを使用することで、コンパイラーが入れ子構造のループを結合できるようになります。オフロードされる入れ子構造のループの反復空間は r ループと c ループの両方を含む 2 次元で、各ターゲットスレッドに並列実行用の 2 次元の反復空間チャンクが割り当てられます。
A、B および C は配列へのポインターとして定義されますが、length はポインターによって参照された float 型配列の要素で指定されます。
この例は、in1 配列の初期化をプロセッサー・グラフィックスへオフロードし、並行してホストで in2 を初期化します。そして、in1 と in2 を使用してホストで 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];
}