手順1でバッファが作成され、手順2でそのバッファが消費され、結果がグローバル配列に組み込まれるような、プロデューサとコンシューマのタスクセットがあります。
何か(+ single
通常parallel
内部)等:
const int buffer_size = 100000;
type global[buffer_size];
type buffer[2][buffer_size];
for(int i=0;i<10000;i++)
{
type *buf[buffer_size] = buffer[i % 2];
#pragma omp task depend(out: [buffer_size]buf)
{
<1. write to buf>
}
#pragma omp task depend(in: [buffer_size]buf) depend(inout: [buffer_size]global)
{
<2. update global using buf>
}
}
これはそれが反復i = 1人の意志のステップ1で、 "遅れ" の1回の繰り返しで、ステップ1とステップ2と重複することを可能にしますステップ2の反復i = 0に並行して実行されます。 反復の重複につき1つのバッファが必要となり、各同時操作は互いに独立して実行できます。
ここで、buf(in
/out
)のセマンティクスは、単純なプロデューサ - 消費者関係であるため、単純です。グローバルでは、現在の配列(in
)を取得し、新しい値を割り当てます(out
)。インプレースでバッファを更新するときには、inout
が必要です。これは、ステップ2のすべてのタスクがこの依存関係のために直列化されることを意味します。つまり、最大で2回の繰り返しが重複している可能性があります(2つ以上のバッファを使用している点はありません)。
これは現在のOpenMP(4.5)タスクの制限ですが、これを解決するa proposal for task reductionsが存在します。代わりに、グローバルをshared
と宣言することもできます。その場合は、アトミック操作を使用して変更する必要があります。
target
句を使用して、GPUでタスク1を実行できます。オフロードされたデバイス(GPU)からのデータがCPUにコピーされていることを確認します。ステップ2のタスクの中で、並列のfor構文を使用してステップ2を並列化するか、タスクを再度実行することができます。
実際には、ステップ1をステップ2と並行して実行し、可能であればマルチスレッドのステップ2も実行する必要があります。ステップ1とステップ2をパラレル化するには、ステップ2で新しいバッファが生成されていることが分かり、ステップ1で古いバッファを上書きするタイミングがわかるように、同期が必要です。ステップ2は要素ごとに1Mサイズの配列要素を調べ、いくつかの条件を満たしている場合は、グローバルバッファ配列内の適切な場所を探して追加してから追加します。 – udyank