C++11 テンプレートの構文は私にとって非常に異質ですが、このような再帰的な問題は、明示的な OpenMP タスクを使用して並列化するのが最適です。
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each(std::tuple<Tp...>& t, FuncT& f)
{
#pragma omp task firstprivate(I) shared(t,f)
{
f(std::get<I>(t));
}
for_each<I + 1, FuncT, Tp...>(t, f);
}
...
// Proper usage
#pragma omp parallel
{
#pragma omp single
for_each(...);
}
for_each
重要な部分は、領域内のsingle
構成でトップレベルの呼び出しを行うことparallel
です。したがって、単一のスレッドのみが を呼び出しfor_each
、その結果f(std::get<I>(t));
、明示的なタスクとして後で実行するためにキューに入れられます。他のスレッドは、コンストラクトの最後にある暗黙のバリアで待機している間single
、タスク キューからタスクのプルを開始し、キューが空になるまでそれらを並行して実行します。わかりやすくするために、タスクで使用されるすべての変数の共有クラスが明示的に示されています。
参照するオブジェクトは共有する必要がt
ありf
、参照自体 (基本的には参照を実装するポインター) は firstprivate にする必要があります。一方、OpenMP 標準では、参照型を firstprivate にすることを禁止しており、コンパイラ ベンダーによって標準の実装方法が異なる傾向があります。インテル C++ コンパイラーは次のコードを受け入れ、タスク内で正しい結果を返しますが、参照される変数はプライベート化されます (これは間違っています)。
void f(int& p)
{
#pragma omp task
{
cout << "p = " << p << endl;
p = 3;
cout << "p' = " << p << endl;
}
}
void f1()
{
int i = 5;
#pragma omp parallel
{
#pragma omp single
f(i);
}
cout << "i = " << i << endl;
}
PGI のコンパイラは正しい結果を提供し、プライベート化は行いませんi
。反対に、GCC はそれが必要であると正しく判断しp
ますfirstprivate
が、標準の禁止事項に遭遇し、コンパイル時エラーが発生します。
タスクを読み取るように変更すると、次のようになります。
#pragma omp task shared(p)
{
...
}
GCC では正しく動作しますが、タスクは間違った初期値を出力p
し、インテル C++ コンパイラーと PGI の C++ コンパイラーの両方でセグメンテーション違反を引き起こします。
図に行く!