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

非同期データ転送について (プロセッサー・グラフィックス)

このトピックは、インテル® メニー・インテグレーテッド・コア (インテル® MIC) アーキテクチャーまたはインテル® グラフィックス・テクノロジーをターゲットとする場合にのみ適用されます。

CPU とターゲットデバイス間でデータを転送するには、すべて in 節またはすべて out 節の offload_transfer プラグマを使用します。signal 節が指定されていない場合、データ転送は同期され、それ以降の文はデータ転送が完了した後に実行されます。

offload_transfersignal 節が指定されると、データ転送は非同期になります。signal 節で指定されるタグは、そのデータセットに関連付けられているアドレス式です。データ転送が開始され、CPU はそのプラグマ以降の文を続行します。

ターゲットデバイスは、タグに関連付けられているすべてのデータの受信または共有が完了すると、後の wait 節が記述されたプラグマで指定されている処理を開始します。データは、データ転送の開始時に指定された変数に格納されます。これらの変数はアクセス可能でなければなりません。

または、インテル® MIC アーキテクチャー上では、非ブロック API _Offload_signaled() を使用して、特定のターゲットデバイスでオフロードコードのセクションの実行が完了したかどうか判断することもできます。

インテル® AVX MIC アーキテクチャー上では、signal 節と wait 節、offload_wait 構造、および _Offload_signaled() API は、特定のターゲットデバイスに関連するため、target() 節で target-number を指定する必要があります。

シグナルが開始される前にシグナルを照会すると、未定義の動作を引き起こし、アプリケーションはランタイムアボートします。例えば、ターゲットデバイス 1 で開始されたシグナル (SIG1) をターゲットデバイス 0 で照会する場合について考えてみます。シグナルはターゲットデバイス 1 で開始されているため、ターゲットデバイス 0 に関連付けられたシグナル (SIG1) はなく、アプリケーションはアボートします。

非同期オフロード中に 1 つのスレッド (スレッド A) でシグナルが作成され、別のスレッドが (スレッド B) そのシグナルを待機する場合、スレッド A が非同期オフロードを開始しシグナルを設定する前はスレッド B がそのシグナルを照会してはなりません。これを行うと、アプリケーションはランタイムアボートします。

signal (tag) 節を使用している場合、if-specifier が FALSE と評価されると、シグナルは未定義となり、このシグナルの待機は未定義の動作を引き起こします。

CPU からターゲットへの非同期データ転送

CPU からターゲットにデータを非同期で転送するには、offload_transfer プラグマの in 節で signal 節を使用します。in 節にリストされる変数によりデータセットが形成されます。プラグマは、CPU からターゲットへこれらの変数のデータ転送を開始します。wait 節を含む以降の offload プラグマ (signal 節で使用された tag と同じ値を使用) で制御された文は、データ転送が完了した後にターゲットで実行されます。

ターゲットから CPU への非同期データ転送

ターゲットから CPU にデータを非同期で転送するには、2 つの異なるプラグマで signal 節と wait 節を使用します。最初のプラグマはデータ転送を開始し、次のプラグマはデータ転送が完了するのを待機します。

インテル® MIC アーキテクチャーの例: ターゲットから CPU と CPU からターゲット

次の例は、CPU とターゲット間のいくつかの非同期データ転送の例を示します。

     1  #include <stdio.h>
     2
     3  __attribute__((target(mic)))
     4             void add_inputs(int N, float *f1, float*f2);
     5
     6  void display_vals(int id, int N, float*f2);
     7
     8  int main()
     9  {
    10     const int N = 5;
    11     float *f1, *f2;
    12     int i, j;
    13
    14     f1 = (float *)_mm_malloc(N*sizeof(float),4096);
    15     f2 = (float *)_mm_malloc(N*sizeof(float),4096);
    16
    17     for (i=0;i<N;i++){
    18        f1[i]=i+1;
    19        f2[i]=0.0;
    20     }
    21

以下の "セクション 1" (行 22 ~ 56) は、CPU とターゲット間の非同期データ転送 (INOUT) と非同期計算を行います。配列 f1f2 のデータ転送は行 28 ~ 30 で開始されます。offload_transfer では計算を開始しません。f1f2 のデータをターゲットへ転送するだけです。行 40 ~ 44 で CPU は、ターゲットでの関数 add_inputs の計算開始を指示し、行 51 の offload_wait まで実行を継続します。オフロード関数は、CPU からあらかじめ転送されたデータ f1f2 を使用します。ターゲットのオフロード領域の実行は、f1f2 の転送が完了し、シグナル (f1) が設定された後に開始されます。ターゲットでオフロード領域を実行している間、CPU は行 51 でオフロード計算が完了し、f2 の結果が CPU に転送されるのを待ちます。f2 のデータが CPU に転送され、シグナル (f2) を受信した後にのみ、CPU で行 51 以降の実行が継続されます。

22     //-----------  セクション 1  --------------------------------------
    23
    24     // f1 と f2 のターゲットへの非同期転送 (IN)
    25     //
    26     // CPU はデータを送信して続行
    27
    28     #pragma offload_transfer target(mic:0)  signal (f1) \
    29                     in( f1 : length(N) alloc_if(1) free_if(0) ) \
    30                     in( f2 : length(N) alloc_if(1) free_if(0) )
    31
    32     // 非同期計算と f2 の CPU への非同期転送 (OUT)
    33     //
    34     // CPU は計算の実行をリクエストして続行
    35     //
    36     // ターゲットはリクエストを受信してあらかじめ送信されたデータを待つ
    37     //  データを受け取った後に計算を実行し、
    38     // 結果を CPU へ同期転送 (OUT)
    39
    40     #pragma offload target(mic:0) wait(f1) signal (f2) \
    41                     in( N ) \
    42                     nocopy( f1 : alloc_if(0) free_if(1) ) \
    43                     out( f2 : length(N) alloc_if(0) free_if(1) )
    44     add_inputs(N, f1, f2);
    45
    46     // オフロード計算の完了を待つ
    47     //
    48     // CPU はオフロード計算と
    49     // f2 の CPU へのデータ転送の完了を待つ
    50
    51     #pragma offload_wait target(mic:0)  wait(f2)
    52
    53
    54     // 現在の値を表示
    55     display_vals(1, N, f2);
    56

このプログラムの "セクション 2" (行 57 ~ 90) は、CPU からターゲットへの非同期データ転送 (IN) と同期計算、そしてターゲットから CPU への非同期転送 (OUT) を含む複数の非同期データ転送を行います。複数の独立した非同期データ転送が発生する可能性があります。offload_transferf1f2 を別々にターゲットに送信しています (行 63 ~ 64 で f1、行 68 ~ 69 で f2)。これらの転送は独立しています。行 81 ~ 85 のターゲットのオフロード領域と関数 add_inputs の実行は、f1f2 の転送が完了し、シグナル (f1f2) が受信された後に開始されます。CPU は、オフロード計算が完了し、f2 の結果が CPU へ転送されるまで実行を待機します。f2 の CPU へのデータ転送は、オフロード領域の実行と同期的に行われます。

    57     //-----------  セクション 2  --------------------------------------
    58
    59     // 独立したターゲットへの非同期転送 (IN)
    60     //
    61     // CPU はデータを送信して続行
    62
    63     #pragma offload_transfer target(mic:0)  signal (f1) \
    64                     in( f1 : length(N) alloc_if(1) free_if(0) )
    65
    66     // CPU はデータを送信して続行
    67
    68     #pragma offload_transfer target(mic:0)  signal (f2) \
    69                     in( f2 : length(N) alloc_if(1) free_if(0) )
    70
    71     // 独立したターゲットへの転送 (IN) を待つ
    72     // 同期計算とデータ転送 (OUT) を行う
    73     //
    74     // CPU は計算の実行をリクエストして
    75     // その完了を待つ
    76     //
    77     // ターゲットはリクエストを受信してあらかじめ送信されたデータを待つ
    78     //データを受け取った後に計算を実行し、
    79     // 結果を CPU へ同期転送 (OUT)
    80
    81     #pragma offload target(mic:0) wait(f1 , f2) \
    82                     in( N ) \
    83                     nocopy( f1 : alloc_if(0) free_if(1) ) \
    84                     out( f2 : length(N) alloc_if(0) free_if(1) )
    85     add_inputs(N, f1, f2);
    86
    87
    88     // 現在の値を表示
    89     display_vals(2, N, f2);
    90

このプログラムの "セクション 3" (行 91 ~ 132) は、独立した CPU からターゲットへの非同期データ転送 (IN)、CPU からターゲットへの同期データ転送 (IN) と計算、独立したターゲットから CPU への非同期データ転送 (OUT) を行います。オフロード関数は、データ f1f2 を使用します。f2 の転送は、あらかじめ行 97 ~ 98 で CPU により開始されています。行 111 ~ 115 のターゲットのオフロード領域の実行は、f1f2 の転送が完了し、f2 の転送のシグナル (f2) が受信された後に開始されます。ターゲットでオフロード領域を実行した後、f2 の結果はターゲットで保持され、CPU は行 116 以降の実行を継続できます。行 122 ~ 123 で、CPU は f2 の計算結果をターゲットから CPU へ非同期データ転送 (OUT) し、行 128 まで実行を継続して、f2 の転送が完了するのを待ちます。f2 のデータが CPU に転送され、シグナル (f2) が受信された後でのみ、CPU で行 128 以降の実行が継続されます。

    91     //-----------  セクション 3  --------------------------------------
    92
    93     // f2 のターゲットへの非同期転送 (IN)
    94     //
    95     // CPU はデータを送信して続行
    96
    97     #pragma offload_transfer target(mic:0)  signal(f2) \
    98                     in( f2 : length(N) alloc_if(1) free_if(0) )
    99
   100     // f1 のターゲットへの非同期転送 (IN) と
   101     // f2 の同期計算。計算結果 f2 は
   102     // ターゲットで保持
   103     //
   104     // CPU は f1 をターゲットへ転送 (IN) し、
   105     // 計算の実行をリクエストしてその完了を待つ
   106     //
   107     // ターゲットはリクエストを受信してあらかじめ送信された
   108     // f2 のデータを待つ。データを受け取った後に計算を実行し、
   109     // 結果はターゲットの f2 に保持
   110
   111     #pragma offload target(mic:0) wait(f2) \
   112                     in( N ) \
   113                     in ( f1 : length(N) alloc_if(1) free_if(0) ) \
   114                     nocopy( f2 )
   115     add_inputs(N, f1, f2);
   116
   117
   118     // CPU はオフロード計算の完了を待ち、
   119     // f2 の CPU への非同期転送 (OUT) を開始して
   120     // 続行
   121
   122     #pragma offload_transfer target(mic:0)  signal (f2) \
   123                     out( f2 : length(N) alloc_if(0) free_if(1) )
   124
   125
   126     // CPU は f2 の CPU への転送の完了を待つ
   127
   128     #pragma offload_wait target(mic:0)  wait(f2)
   129
   130     // 現在の値を表示
   131     display_vals(3, N, f2);
   132
   133  }
   134
   135  void add_inputs (int N, float *f1, float*f2)
   136  {
   137     int i;
   138
   139     for (i=0; i<N; i++){
   140        f2[i] = f2[i] + f1[i];
   141     }
   142  }
   143
   144  void display_vals (int id, int N, float *f2)
   145  {
   146     int i;
   147
   148     printf("\nResults after Offload #%d:\n",id);
   149     for (i=0; i<N; i++){
   150       printf("     f2[%d]= %f\n",i,f2[i]);
   151     }
   152  }

次の例は、オフロードの入力をダブルバッファーにしています。

#pragma offload_attribute(push, target(mic))
int count = 25000000;
int iter = 10;
float *in1, *out1;
float *in2, *out2;
#pragma offload_attribute(pop)

void do_async_in()
{
  int i;
  #pragma offload_transfer target(mic:0) in(in1 : length(count) alloc_if(0) free_if(0) ) signal(in1)
  for (i=0; i<iter; i++)
  {
    if (i%2 == 0) {
          #pragma offload_transfer target(mic:0) if(i!=iter-1) in(in2 : length(count) alloc_if(0) free_if(0) ) signal(in2)
          #pragma offload target(mic:0) nocopy(in1) wait(in1) out(out1 : length(count) alloc_if(0) free_if(0) )
          compute(in1, out1);
    } else {
          #pragma offload_transfer target(mic:0) if(i!=iter-1) in(in1 : length(count) alloc_if(0) free_if(0) ) signal(in1)
          #pragma offload target(mic:0) nocopy(in2) wait(in2) out(out2 : length(count) alloc_if(0) free_if(0) )
          compute(in2, out2);
    }
  }
}

CPU とプロセッサー・グラフィックス間の非同期転送の例

この例は、プロセッサー・グラフィックス上で非同期リソース割り当てとプロセッサー・グラフィックスと CPU 間で非同期データ転送を行います。これは、コンパイラーと実行時の最適化に応じて、物理的共有として実装することが可能です。

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

    const int size = N * sizeof(int);
    int* in1 = (int*)malloc(size);
    int* in2 = (int*)malloc(size);
    int* in3 = (int*)malloc(size);
    int* out = (int*)malloc(size);

    // CPU で最初の 2 つの入力配列 in1 と in2 を初期化する
    _Cilk_for (int i = 0; i < N; i++) {
        in1[i] = i+1;
    }
    _Cilk_for (int i = 0; i < N; i++) {
        in2[i] = 2-i;
    }

    // プロセッサー・グラフィックスへの in1 と in2 の非同期データ転送を開始する
    // 結果 (out) の領域を割り当てる
#pragma offload_transfer target(gfx)     \
    in(in1, in2 : length(N) alloc_if(1)) \
    nocopy(out: length(N) alloc_if(1))   \
    signal(in1)

    // ... CPU で 3 つ目の入力配列の初期化を開始する
    // 
    _Cilk_for (int i = 0; i < N; i++) {
        in3[i] = 5*i*i - 3*i;
    }

    // in1、in2、out の転送と割り当てが完了したら
    // out 値の一部を計算するプロセッサー・グラフィックスのタスクを非同期に開始する
#pragma offload target(gfx)                  \
    nocopy(in1, in2 : length(N) alloc_if(0)) \
    out(out:length(N) alloc_if(0))           \
    pin(in3 : length(N))                     \
    wait(in1)                                \
    signal(out)
    _Cilk_for (int i = 0; i < N/2; i++) {
        out[i] = in1[i] + in2[i] + in3[i] - 3*i*i + i - 3;
    }

    // out の残り部分を計算する
    _Cilk_for (int i = N/2; i < N; i++) {
        out[i] = in1[i] + in2[i] + in3[i] - 3*i*i + i - 3;
    } 

    // out の計算の完了を待機し、プロセッサー・グラフィックス上のリソースを解放する
    // (共有リソース in3 は解放済み)
#pragma offload_transfer target(gfx)              \
    wait(out)                                     \
    nocopy(in1, in2, out : length(N) free_if(1))

関連情報