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

ポインター変数のメモリー割り当ての管理

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

オフロードされたプログラムで使用されるポインター変数に対する CPU のメモリー管理は、オフロードされないプログラムと同様です。つまり、offload プラグマは CPU のメモリー割り当てと解放に影響しません。通常どおり、プログラマーが指定する必要があります。

#pragma offload の in 節と out 節で指定されるポインター変数に対するコプロセッサーのメモリー管理は、コンパイラーとランタイムシステムによって自動的に行われます。

入力ポインター変数に対するターゲットのメモリー管理

#pragma offload の in 変数は、デフォルトでは、各ポインター変数に新しいメモリーを割り当てます。構造からリターンするときに割り当てられたメモリーは解放されます。オフロード間でデータを保持するには、alloc_iffree_if 修飾子を使用して、コプロセッサーのデフォルトメモリー割り当て動作を変更します。

alloc_if 修飾子は、構造がターゲットで実行されるときに in 節のポインター変数がターゲットに新しいメモリーブロックを割り当てるかどうかを制御するブール条件を指定します。式が TRUE と評価されると、節でリストされている各変数に新しいメモリーが割り当てられます。条件が FALSE と評価されると、ターゲットの既存のポインター値が再利用されます。前のオフロードに対して free_if(FALSE) 節を使用し、十分なサイズのメモリーブロックがターゲットの変数に割り当てられていることを確認する必要があります。

free_if 修飾子は、in 節のポインター変数に割り当てたメモリーを解放するかどうかを制御するブール条件を指定します。式が TRUE と評価されると、節でリストされている各変数に割り当てられたメモリーは解放されます。条件が FALSE と評価されると、リストの各変数に割り当てられたメモリーに対する操作は行われません。割り当てられたメモリーは後の節で再利用できます。

alloc_if および free_ifブール式は、構造がターゲットにオフロードされたときに CPU で評価されます。

出力ポインター変数に対するターゲットのメモリー管理

デフォルトでは、out 変数にはオフロードの開始時にターゲットの新しいメモリーが割り当てられ、オフロードの終了時に割り当てられたメモリーは解放されます。alloc_if および free_if 修飾子は、このデフォルトの動作を変更します。式はホストで評価され、コプロセッサーのメモリー割り当てを制御するために使用されます。

出力値がホストで受け取られた場合、メモリー割り当ては行われません。ホストで結果を受け取るには、out 節にリストされた変数は、十分なサイズが割り当てられたメモリーを指している必要があります。

ターゲット上の事前に割り当てられているメモリーへのデータの転送

前のセクションで説明したように、free_if 修飾子を false に設定すると、inoutinout、または nocopy 節のポインター変数は、ターゲットのメモリー割り当てを保持できます。inoutinout、または nocopy 節を使用して alloc_if(0) と指定することで、以降のオフロードでそのメモリーを再利用できます。ターゲットメモリーが割り当てられると、inoutinout、または nocopy 節のデスティネーションとして使用される CPU ポインター変数の値と関連付けられます。再利用する場合は、転送のデスティネーションである CPU ポインター変数の値を使用して特定します。ターゲットメモリーが割り当てられたときに使用される CPU アドレスとターゲットメモリーの関連付けは、オフロード・ランタイム・ライブラリーにより自動的に維持されます。この関連付けは、ターゲットメモリーの割り当てまたは解放とともに、作成あるいは削除されます。alloc_if(1) free_if(0) を使用して割り当て時に関連付けを作成し、free_if(1) を使用して解放時に関連付けを削除します。

CPU のスタティック・データへのポインターは特殊なケースです。次の 2 つの条件がどちらも満たされる場合、alloc_if および free_if 修飾子は無視されます。

ターゲットの静的に割り当てられたメモリーは転送のデスティネーションとして使用されます。このターゲットメモリーは動的に割り当てられず、解放されません。

1 つの CPU アドレスと関連付けられるのは、ターゲットメモリーの 1 つのブロックのみです。既存の関連付けを削除する前に同じアドレスに対して alloc_if(1) を呼び出して 2 つ目の関連付けを作成しないでください。新しい関連付けが以前の関連付けに上書きされ、ターゲットのメモリーリークを引き起こす可能性があります。

一致する関連付けが見つからない場合、転送されたポインターに対して free_if(1) を呼び出しても処理は行われません。関連付けの削除は無視されます。関連付けは、1 つの CPU アドレス、特定長、および範囲内の異なる CPU アドレスで作成された別の関連付けから作成することもできます。元のアドレスが異なるため、alloc_if および free_if を使用して個別にターゲットの割り当てを作成できます。

ポインター変数のアライメント

メモリーがターゲットのポインター変数に割り当てられると、ポインターが指しているデータ型の自然境界でアライメントされます。例えば、プログラムで (より厳格なアライメント要件でデータを処理する) アセンブリー・コード、組込み関数、配列表記 (アレイ・ノーテーション) を使用することを予定している場合、データをより大きな境界でアライメントする必要があります。このような場合、align 修飾子を使用してアライメントを指定します。align 修飾子の演算子は 2 の累乗を評価する整数式でなければなりません。式はホストで評価され、ターゲットのポインターに割り当てられたメモリーの領域は式の値以上の境界でアライメントされます。出力値がホストで受け取られた場合、メモリー割り当ては行われません。結果を受け取るには、out 節にリストされた変数は、十分なサイズが割り当てられたメモリーを指している必要があります。

データ転送のパフォーマンスが最適になるように、デフォルトで、ポインターによる転送のターゲット・メモリー・アドレスは、CPU データの 64 バイト以内のオフセットと一致するように設定されます。つまり、CPU のソースアドレスが 64 バイト境界を 16 バイト超える場合、ターゲットのデータアドレスも 64 バイト境界を 16 バイト超えます。

align 修飾子を指定すると、デフォルト設定は無視され、指定したアライメントでターゲットメモリーがアライメントされます。高速なデータ転送の利点を活用し、ターゲットで必要なアライメントを得るためには、CPU データがターゲットで要求されるアライメントと同じ境界でアライメントされていることを確認してください。同じ境界でアライメントすることで、高速なデータ転送の要件とターゲットデータのアライメントの要件が満たされます。

ここで説明した offload 節の修飾子を理解するため、次のマクロについて考えてみます。

#define ALLOC   alloc_if(1)
#define FREE    free_if(1)
#define RETAIN  free_if(0)
#define REUSE   alloc_if(0)

次の例は、コプロセッサーがデータを保持しない、デフォルトの動作を示しています。

コンパイラーが、オフロードで処理するデータを割り当てて解放します。allocfree 修飾子は必要ありません。

#pragma offload target(mic) in( p:length(l) )   
...

次の例は、オフロード間でコプロセッサーがデータを保持する場合を示しています。

次のコードは、このオフロードの一部として p にメモリーを割り当て、オフロード後に p に割り当てたメモリーを保持します。

ALLOC はデフォルトで、明示的に指定する必要はないことに注意してください。

#pragma offload target(mic) in (p:length(l) ALLOC RETAIN) 
...

次のコードは、p に以前割り当てられたメモリーを再利用します。メモリーに新しいデータのみを転送し、オフロード完了後にメモリーを保持します。

#pragma offload target(mic) in (p:length(l) REUSE RETAIN) 
...

次のコードは、p に以前割り当てられたメモリーを再利用します。ただし、このオフロード後に p に割り当てたメモリーを解放します。

FREE はデフォルトで、明示的に指定する必要はないことに注意してください。

#pragma offload target(mic) in (p:length(l) REUSE FREE)

次のコードは、ポインターを使用してターゲットのメモリー割り当てを作成しています。その後、ポインター値を別の関数に渡します。ポインター値により、ターゲットメモリーを再利用できます。#pragma offload_transfer 文が配列表記表記を使用していることに注意してください。配列表記で変数が指定される場合、length 修飾子は必要ありません。

// 関数引数により転送
int *p = malloc(…);
int count;
void bar()
{
	…
	// コプロセッサー上のメモリーを割り当ててデータを転送
	#pragma offload_transfer … in( p[0:count] : RETAIN )
	foo(p, l);
}
foo(int *arg_p, int count)
{
	// 転送は成功する
	#pragma offload … in( arg_p[0:count] : REUSE )
	…
}

次のコードは、スタティック・データをターゲットに転送します。CPU 変数と一致するターゲットのスタティック・データ割り当てが自動的に使用されます。

// bar が array_cpu_only で呼び出された場合はターゲットの動的メモリーを使用
// bar が array_both で呼び出された場合はターゲットの array_both を使用

__declspec(target(mic)) int array_both[1000];
int array_cpu_only[1000];
void foo()
{
	bar(&array_cpu_only[0]);
	bar(&array_both[0]);
}
void bar(int *p, int count)
{
	#pragma offload … in(p[0:count] REUSE)
	…
}

次のコードは、CPU とターゲット間で、スタティックへのポインターと動的に割り当てられたメモリーへのポインターを使用しています。

// 変数のオフロードにより作成される関連付けと動的に割り当てられた
// 変数へのポインターは同じ方法で扱われる

__declspec(target(mic)) float array[1000];
main()
{
	// 配列をターゲットにコピー
	#pragma offload target(mic) in(array)
    	{   ...  }

	// bar2 はターゲットで動的に割り当てられたメモリーを使用
    	printf("%e\n", bar2());

	// bar1 は静的に割り当てられた "配列" を使用
   	printf("%e\n", bar1(&array[0], 100));
}
float bar2()
{
    float * my_p = malloc(100 * sizeof(float));
    #pragma offload target(mic) in(my_p[0:100] : RETAIN )
    {  ...  }
    return bar1(my_p, 100);
}
float bar1(float *p, int n)
{
    #pragma offload target(mic) IN(p : length(0) REUSE RETAIN)
    {  sum = … <sum of elements in p>  }
	return sum;
}