インテル® C++ コンパイラー 18.0 デベロッパー・ガイドおよびリファレンス
このトピックは、インテル® メニー・インテグレーテッド・コア (インテル® MIC) アーキテクチャーまたはインテル® グラフィックス・テクノロジーをターゲットとする場合にのみ適用されます。
CPU とターゲットデバイス間でデータを転送するには、すべて in 節またはすべて out 節の offload_transfer プラグマを使用します。signal 節が指定されていない場合、データ転送は同期され、それ以降の文はデータ転送が完了した後に実行されます。
offload_transfer に signal 節が指定されると、データ転送は非同期になります。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 からターゲットにデータを非同期で転送するには、offload_transfer プラグマの in 節で signal 節を使用します。in 節にリストされる変数によりデータセットが形成されます。プラグマは、CPU からターゲットへこれらの変数のデータ転送を開始します。wait 節を含む以降の offload プラグマ (signal 節で使用された tag と同じ値を使用) で制御された文は、データ転送が完了した後にターゲットで実行されます。
ターゲットから CPU にデータを非同期で転送するには、2 つの異なるプラグマで signal 節と wait 節を使用します。最初のプラグマはデータ転送を開始し、次のプラグマはデータ転送が完了するのを待機します。
次の例は、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 とターゲット間の非同期データ転送 (IN と OUT) と非同期計算を行います。配列 f1 と f2 のデータ転送は行 28 ~ 30 で開始されます。offload_transfer では計算を開始しません。f1 と f2 のデータをターゲットへ転送するだけです。行 40 ~ 44 で CPU は、ターゲットでの関数 add_inputs の計算開始を指示し、行 51 の offload_wait まで実行を継続します。オフロード関数は、CPU からあらかじめ転送されたデータ f1 と f2 を使用します。ターゲットのオフロード領域の実行は、f1 と f2 の転送が完了し、シグナル (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_transfer は f1 と f2 を別々にターゲットに送信しています (行 63 ~ 64 で f1、行 68 ~ 69 で f2)。これらの転送は独立しています。行 81 ~ 85 のターゲットのオフロード領域と関数 add_inputs の実行は、f1 と f2 の転送が完了し、シグナル (f1 と f2) が受信された後に開始されます。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) を行います。オフロード関数は、データ f1 と f2 を使用します。f2 の転送は、あらかじめ行 97 ~ 98 で CPU により開始されています。行 111 ~ 115 のターゲットのオフロード領域の実行は、f1 と f2 の転送が完了し、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 間で非同期データ転送を行います。これは、コンパイラーと実行時の最適化に応じて、物理的共有として実装することが可能です。
インテル® 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))