OpenMP標準は、C++ 98(ISO/IEC 14882:1998)のみを考慮します。これは、C++ 03またはC++ 11でのOpenMPの使用をサポートする標準がないことを意味します。したがって、C++> 98およびOpenMPを使用するプログラムはすべて標準外で動作します。つまり、特定の条件下で動作する場合でも、移植性は低い可能性がありますが、絶対に保証されることはありません。
独自のマルチスレッドサポートを備えたC++ 11の場合、状況はさらに悪化します。これは、特定の実装ではOpenMPと衝突する可能性が非常に高くなります。
では、OpenMPをC++ 03およびC++ 11で使用することはどれほど安全ですか?
1つの同じプログラムでC++ 11マルチスレッドとOpenMPを安全に使用できますが、それらをインターリーブすることはありません(つまり、C++ 11並行機能に渡されるコードにOpenMPステートメントがなく、スレッドにC++ 11同時実行性がありません) OpenMPによって生成されます)?
私は、最初にOpenMPを使用していくつかのコードを呼び出し、次に同じデータ構造でC++ 11同時実行性を使用して他のコードを呼び出す状況に特に興味があります。
ウォルター、私はあなたに 他の議論 の現状を伝えただけでなく、ソースから直接(つまり、OpenMP言語委員会の一部である私の同僚から)情報を提供したと思います。 。
OpenMPは、FORTRANおよびCへの軽量のデータ並列追加として設計され、後にC++イテレーター(ランダムアクセスイテレーター上の並列ループなど)および明示的なタスクの導入によるタスク並列処理に拡張されました。これは、可能な限り多くのプラットフォーム間でポータブルであり、3つの言語すべてで本質的に同じ機能を提供することを目的としています。その実行モデルは非常に単純です。シングルスレッドアプリケーションは、並列領域でスレッドのチームをフォークし、内部でいくつかの計算タスクを実行してから、チームを結合してシリアル実行に戻します。ネストされた並列処理が有効になっている場合、並列チームの各スレッドは後で独自のチームをフォークできます。
OpenMPの主な用途はハイパフォーマンスコンピューティングであるため(結局のところ、そのディレクティブと実行モデルはHigh Performance Fortranから借用されています)、OpenMP実装の主な目標は効率他のスレッドパラダイムとの相互運用性ではありません。一部のプラットフォームでは、OpenMPランタイムがプロセススレッドを制御する唯一のものである場合にのみ、効率的な実装を実現できます。また、OpenMPには、他のスレッド構造ではうまく機能しない可能性のある特定の側面があります。たとえば、2つ以上の同時並列領域をフォークするときにOMP_THREAD_LIMIT
によって設定されるスレッド数の制限などです。
OpenMP標準自体は他のスレッドパラダイムの使用を厳密に禁止していませんが、それらとの相互運用性を標準化していないため、そのような機能のサポートは実装者次第です。これは、一部の実装がトップレベルのOpenMPリージョンの安全な同時実行を提供する場合と、提供しない場合があることを意味します。 x86の実装者は、それをサポートすることを約束します。これは、それらのほとんどが他の実行モデル(たとえば、CilkとTBBを備えたIntel、C++ 11を備えたGCCなど)の支持者でもあり、x86は通常「実験的」プラットフォームと見なされているためです(他のベンダーは通常、はるかに保守的です)。
OpenMP 4.0は、採用しているC++機能についてもISO/IEC 14882:1998を超えていません(SC12ドラフトは ここ です)。この標準には、ポータブルスレッドアフィニティなどが含まれるようになりました。これは、OpenMPのバインディングメカニズムと衝突する独自のバインディングメカニズムを提供する可能性のある他のスレッドパラダイムとは明らかにうまく機能しません。繰り返しになりますが、OpenMP言語はHPC(データとタスクの並列科学およびエンジニアリングアプリケーション)を対象としています。 C++ 11構造は、汎用コンピューティングアプリケーションを対象としています。派手なC++ 11同時実行機能が必要な場合は、C++ 11のみを使用します。または、本当にOpenMPと組み合わせる必要がある場合は、移植性を維持したい場合は、言語機能のC++ 98サブセットに固執します。
私は、最初にOpenMPを使用していくつかのコードを呼び出し、次に同じデータ構造でC++ 11同時実行性を使用して他のコードを呼び出す状況に特に興味があります。
不可能にしたいことの明白な理由はありませんが、それはOpenMPコンパイラーとランタイム次第です。並列実行にOpenMPを使用する無料の商用ライブラリ(MKLなど)がありますが、何がいつ可能かについての情報を提供するマルチスレッドコードとの非互換性の可能性についての警告が常にあります(ユーザーマニュアルに深く隠されている場合もあります)。いつものように、これはOpenMP標準、したがってYMMVの範囲外です。
私は実際にハイパフォーマンスコンピューティングに興味がありますが、OpenMPは(現在)私の目的を十分に果たしていません:それは十分に柔軟性がありません(私のアルゴリズムはループベースではありません)
たぶんあなたは本当に探しています [〜#〜] tbb [〜#〜] ?これは、標準のC++で、ループおよびタスクベースの並列処理、およびさまざまな並列データ構造のサポートを提供し、ポータブルでオープンソースです。
(完全な免責事項:私はTBBに深く関わっているIntelで働いていますが、実際にはonTBBではなくOpenMPで働いています:-);私は確かにIntelのために話しているのではありません!)。
ジム・カウニーのように、私もインテルの従業員です。 Intel Threading Building Blocks(Intel TBB)は、OpenMPのようなループレベルの並列処理だけでなく、他の並列アルゴリズム、並行コンテナー、および低レベルの機能も備えているため、優れたオプションである可能性があることに同意します。そしてTBBは現在のC++標準に追いつくように努めます。
また、Walterを明確にするために、Intel TBBには、parallel_reduceアルゴリズムと、アトミックおよびミューテックスの高レベルのサポートが含まれています。
インテル®スレッディングビルディングブロックのユーザーガイドは、 http://software.intel.com/sites/products/documentation/doclib/tbb_sa/help/tbb_userguide/title.htm ユーザーガイドに記載されています。ライブラリの機能の概要。
OpenMPは多くの場合(例外はありません)Pthreadの上に実装されているため、C++ 11の同時実行性がPthreadコードとどのように相互運用するかを考えることで、相互運用性に関する質問のいくつかについて推論できます。
複数のスレッドモデルの使用によるオーバーサブスクリプションが問題であるかどうかはわかりませんが、これは間違いなくOpenMPの問題です。 OpenMP 5でこれに対処するための 提案 があります。それまでは、これを解決する方法は実装によって定義されます。これらは重いハンマーですが、 OMP_WAIT_POLICY
(OpenMP 4.5 +)、 KMP_BLOCKTIME
(IntelおよびLLVM)、および GOMP_SPINCOUNT
(GCC)これに対処します。他の実装にも似たようなものがあると確信しています。
相互運用性が真の懸念事項である1つの問題は、w.r.tです。メモリモデル、つまりアトミック操作の動作。これは現在未定義ですが、それでも理由はあります。たとえば、OpenMP並列処理でC++ 11アトミックを使用する場合は問題ありませんが、OpenMPスレッドからC++ 11アトミックを正しく使用する必要があります。
OpenMPアトミックとC++ 11アトミックを混合することは悪い考えです。私たち(OpenMP 5ベース言語サポートの調査を担当するOpenMP言語委員会ワーキンググループ)は現在、これを整理しようとしています。個人的には、C++ 11アトミックはあらゆる点でOpenMPアトミックよりも優れていると思うので、アトミックにはC++ 11(またはC11、または __atomic
)を使用することをお勧めします。そして、Fortranプログラマーのために#pragma omp atomic
を残します。
以下は サンプルコード OpenMPスレッドでC++ 11アトミックを使用します。それは私がそれをテストしたどこでも設計通りに動作します。
完全な開示:ジムやマイクのように、私はインテルで働いています:-)
#if defined(__cplusplus) && (__cplusplus >= 201103L)
#include <iostream>
#include <iomanip>
#include <atomic>
#include <chrono>
#ifdef _OPENMP
# include <omp.h>
#else
# error No OpenMP support!
#endif
#ifdef SEQUENTIAL_CONSISTENCY
auto load_model = std::memory_order_seq_cst;
auto store_model = std::memory_order_seq_cst;
#else
auto load_model = std::memory_order_acquire;
auto store_model = std::memory_order_release;
#endif
int main(int argc, char * argv[])
{
int nt = omp_get_max_threads();
#if 1
if (nt != 2) omp_set_num_threads(2);
#else
if (nt < 2) omp_set_num_threads(2);
if (nt % 2 != 0) omp_set_num_threads(nt-1);
#endif
int iterations = (argc>1) ? atoi(argv[1]) : 1000000;
std::cout << "thread ping-pong benchmark\n";
std::cout << "num threads = " << omp_get_max_threads() << "\n";
std::cout << "iterations = " << iterations << "\n";
#ifdef SEQUENTIAL_CONSISTENCY
std::cout << "memory model = " << "seq_cst";
#else
std::cout << "memory model = " << "acq-rel";
#endif
std::cout << std::endl;
std::atomic<int> left_ready = {-1};
std::atomic<int> right_ready = {-1};
int left_payload = 0;
int right_payload = 0;
#pragma omp parallel
{
int me = omp_get_thread_num();
/// 0=left 1=right
bool parity = (me % 2 == 0);
int junk = 0;
/// START TIME
#pragma omp barrier
std::chrono::high_resolution_clock::time_point t0 = std::chrono::high_resolution_clock::now();
for (int i=0; i<iterations; ++i) {
if (parity) {
/// send to left
left_payload = i;
left_ready.store(i, store_model);
/// recv from right
while (i != right_ready.load(load_model));
//std::cout << i << ": left received " << right_payload << std::endl;
junk += right_payload;
} else {
/// recv from left
while (i != left_ready.load(load_model));
//std::cout << i << ": right received " << left_payload << std::endl;
junk += left_payload;
///send to right
right_payload = i;
right_ready.store(i, store_model);
}
}
/// STOP TIME
#pragma omp barrier
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
/// PRINT TIME
std::chrono::duration<double> dt = std::chrono::duration_cast<std::chrono::duration<double>>(t1-t0);
#pragma omp critical
{
std::cout << "total time elapsed = " << dt.count() << "\n";
std::cout << "time per iteration = " << dt.count()/iterations << "\n";
std::cout << junk << std::endl;
}
}
return 0;
}
#else // C++11
#error You need C++11 for this test!
#endif // C++11
OpenMP 5.0は、C++ 11への相互作用を定義するようになりました。ただし、通常はC++ 11以降のものを使用します"不特定の動作が発生する可能性があります"。
このOpenMPAPI仕様では、ISO/IEC 14882:2011をC++ 11と呼んでいます。 OpenMP仕様の将来のバージョンでは、次の機能に対応する予定ですが、現在、それらを使用すると、動作が特定されない可能性があります。