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

非同期データ転送について

このトピックは、インテル® メニー・インテグレーテッド・コア (インテル® MIC) アーキテクチャーにのみ適用されます。

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

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

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

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

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 節を使用します。 最初のプラグマはデータ転送を開始し、次のプラグマはデータ転送が完了するのを待機します。

コプロセッサーから 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);
    }
  }
}

関連情報