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

共有ローカルメモリーに関するプログラミングの制約事項

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

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

第 1 レベルの並列ループの入れ子

__declspec(gfx_kernel) 属性で宣言された関数の並列ループが、第 1 レベルの並列ループの入れ子 (_Thread_group が付きます) になることがあります。

第 1 レベルの並列ループの入れ子内では、次の構造のみ許可されます。

チャンクサイズはすべての次元で 1 になることが保証されています。そのため、各スレッドグループは、第 1 レベルのループの入れ子の 1 つの反復だけを実行します。

シリアルコード

シリアルコードとは、第 2 レベルの並列ループの入れ子の構文に含まれない、第 1 レベルの並列ループの入れ子内のコードを指します。例えば、次のサンプルの行 4 ~ 5 です。

01 __declspec(target(gfx_kernel)) void slm_enabled_kernel(int *data, int param) {
02   _Cilk_for _Thread_group (...){
03     ...
04     int lo = param/2; // シリアルコード
05     int up = param*2; // シリアルコード
06 
07     _Cilk_for (int j = lo; j < up; j++) {
08       ...
09     }
10   }
11 }

前述のシリアルコードは、各スレッドグループのマスタースレッドによって実行されます。上記のサンプルの行 7 にある入れ子構造の _Cilk_for ループのような並列構造に到達すると、マスタースレッドはグループ内のスレッド間で実行を分割します。

以下は、SLM を使用する行列乗算プログラムの一部です。この例からシリアルコードの制約事項が分かります。

01  _Cilk_for _Thread_group (int tg_y = 0; tg_y < Y; tg_y += SLM_TILE_Y) {     
02      _Cilk_for _Thread_group (int tg_x = 0; tg_x < X; tg_x += SLM_TILE_X) {
03          // SLM に割り当てられる各行列の "スーパータイル" を宣言します
04          __thread_group_local float slm_atile[SLM_TILE_Y][SLM_TILE_K];
05          __thread_group_local float slm_btile[SLM_TILE_K][SLM_TILE_X];
06          __thread_group_local float slm_ctile[SLM_TILE_Y][SLM_TILE_X];
07  
08          // スーパータイルを (並列に) 初期化します
09          _Cilk_for (int i = 0; i < SLM_TILE_Y; i++)
10           _Cilk_for (int j = 0; j < SLM_TILE_X; j++)
11            slm_ctile[i][j] = 0.0;
12  
13          // 現在の A のスーパータイル行と B のスーパータイル列の
14          // ドット積を計算します
15          for (int super_k = 0; super_k < K; super_k += SLM_TILE_K) {
16              // 並列実行
17              // A と B の "スーパータイル" を SLM に (並列に) キャッシュします
18              slm_atile[:][:] = A[tg_y:SLM_TILE_Y][super_k:SLM_TILE_K];
19              slm_btile[:][:] = B[super_k:SLM_TILE_Y][tg_x:SLM_TILE_X];
20              
21              // コピーが完了するまですべてのスレッドが待機します
22              _gfx_gpgpu_thread_barrier();
23              
24              // 並列実行
25              // タイル化された行列乗算アルゴリズムを使用して
26              // 通常の行列のようにスーパータイルの乗算を (並列に) 実行します
27              _Cilk_for (int t_y = 0; t_y < SLM_TILE_Y; t_y += TILE_Y) {
28                  _Cilk_for (int t_x = 0; t_x < SLM_TILE_X; t_x += TILE_X) {

行 1 ~ 2 は第 1 レベルの並列ループの入れ子、行 15 はシリアルコード、行 18 ~ 19 と行 27 ~ 28 は第 2 レベルの並列ループの入れ子です。シリアルコードは、スーパータイルのドット積を計算するループです。この計算は各スレッドグループによって実行され、このサイクルはスレッドグループ内のスレッド間で並列化されません。

グループ内のスレッドはすべて同じシリアルコードを実行します。同じスレッドグループ内の異なるスレッドが異なる結果になることはありません。また、現在のスレッド以外にアクセスできます。シリアルコードには次の制約事項があります。

第 2 レベルの並列ループの入れ子

スレッド・グループ・ローカル・データ

スレッド・グループ・ローカル・データには、次の構文の制約事項とセマンティクスが適用されます。

種類

制約事項

変数宣言

_Cilk_for _Thread_group (...){
  _Cilk_for _Thread_group (...){
    __thread_group_local int slm_data[N][M];
    ...
  }
}

問題なし。有効な SLM データ宣言。

変数宣言

__thread_group_local int slm_data[N][M];
_Cilk_for _Thread_group (...){
  _Cilk_for _Thread_group (...){
    ...
  }
}

無効な SLM データ宣言。_Cilk_for 入れ子構造のスレッドグループ内に入れ子されなければなりません。

ポインター宣言

_Cilk_for _Thread_group (...){
  float * __thread_group_local p;
  ...
}

問題なし。SLM に割り当てられるポインターの宣言。

ポインター宣言

float __thread_group_local *p; 
              

問題なし。SLM に割り当てられたデータへのポインターの宣言。

クラス・オブジェクト

class c1 {
    int i1;
    c1() {i1 = 10;}
}
...
__thread_group_local c1 obj;

無効。将来、言語拡張によりサポートされる可能性があります。

戻り値の型

float __thread_group_local * bar(...)

問題なし。戻り値はポインター型。

戻り値の型

float __thread_group_local  bar(...)

問題なし。rval の型修飾子が不適切ですが、許可されます/無視されます。

構造体フィールド

struct s1 {
    __thread_group_local float *fld;
    ...
}

宣言は問題なし。一部のコンテキストでは利用できません。

構造体フィールド

struct s1 {
    ...
    __thread_group_local float fld1;
}

許可されません。変数全体を __thread_local_group にする必要があります。

構造体

Struct __thread_group_local s1 {
    float tgl[N][M];
}

問題なし。一般に SLM データは配列であることが想定されますが、任意のデータが許可されます。

並列ループ

__declspec(target(gfx)) void foo(...){
    _Cilk_for (...){...}
}
...
#pragma offload target(gfx)
_Cilk_for _Thread_group(...){
    ...
    foo(...);
}

foo がインライン展開されていない場合、このようなプラグマの定義はエラーと診断されます。ただし、言語上は問題ありません。