OpenMPは、 C、C++、Fortranでのマルチプラットフォーム共有メモリマルチプロセッシングプログラミングをサポートするアプリケーションプログラミングインターフェイス(API) であり、Solaris、AIX、FreeBSD、HP-UX、Linux、macOS、Windows、OpenHarmonyなど、多くのプラットフォーム、命令セットアーキテクチャ、オペレーティングシステムで動作します。 [ 3 ]これは、実行時動作に影響を与えるコンパイラディレクティブ、ライブラリルーチン、環境変数のセットで構成されています。 [ 2 ] [ 4 ] [ 5 ] [ 6 ]
OpenMP は、 Arm、AMD、IBM、Intel、Cray、HP、富士通、Nvidia、NEC、 Red Hat、Texas Instruments、Oracle Corporation など、幅広い主要なコンピュータ ハードウェアおよびソフトウェア ベンダーによって共同で定義された非営利技術コンソーシアムである OpenMP Architecture Review Board (またはOpenMP ARB)によって管理されています。[ 1 ]
OpenMPは、移植性と拡張性に優れたモデルを採用しており、プログラマーは標準的なデスクトップコンピュータからスーパーコンピュータまで、幅広いプラットフォーム向けに並列アプリケーションを開発するためのシンプルで柔軟なインターフェースを利用できます。
並列プログラミングのハイブリッドモデルで構築されたアプリケーションは、OpenMPとMessage Passing Interface(MPI)の両方を使用してコンピュータクラスタ上で実行できます。この場合、OpenMPは(マルチコア)ノード内の並列処理に使用され、MPIはノード間の並列処理に使用されます。また、ソフトウェア分散共有メモリシステム上でOpenMPを実行する[ 7 ] 、 OpenMPをMPIに変換する[ 8 ] [ 9 ] 、共有メモリシステム以外のシステム向けにOpenMPを拡張する[ 10 ]などの取り組み も行われています。

OpenMPはマルチスレッド処理の実装であり、プライマリスレッド(連続して実行される一連の命令)が指定された数のサブスレッドを分岐させ、システムがタスクをそれらのサブスレッドに分割する並列化手法です。これらのスレッドは並行して実行され、ランタイム環境はスレッドを異なるプロセッサに割り当てます。
並列実行されるコード部分は、その部分が実行される前にスレッドが形成されるようにするコンパイラディレクティブによって適切にマークされます。[ 3 ]各スレッドには、関数(と呼ばれる)を使用して取得できるIDが関連付けられています。スレッドIDは整数であり、プライマリスレッドのIDは0です。並列化されたコードの実行後、スレッドはプライマリスレッドに合流し、プライマリスレッドはプログラムの最後まで続行します。omp_get_thread_num()
デフォルトでは、各スレッドは並列化されたコード部分を独立して実行します。ワークシェアリング構造を使用すると、タスクをスレッド間で分割し、各スレッドが割り当てられたコード部分を実行するようにできます。OpenMPを使用すれば、タスク並列処理とデータ並列処理の両方を実現できます。
ランタイム環境は、使用状況、マシンの負荷、その他の要因に応じて、スレッドをプロセッサに割り当てます。ランタイム環境は、環境変数に基づいてスレッド数を割り当てることも、コードが関数を使用して割り当てることもできます。OpenMP 関数は、C / C++のomp.hというヘッダーファイルに含まれています。
OpenMPアーキテクチャレビューボード(ARB)は、1997年10月に最初のAPI仕様であるOpenMP for Fortran 1.0を公開しました。翌年の10月にはC/C++規格をリリースしました。2000年にはFortran仕様のバージョン2.0が、2002年にはC/C++仕様のバージョン2.0がリリースされました。バージョン2.5は、C/C++とFortranを統合した仕様で、2005年にリリースされました。
バージョン 2.0 までは、OpenMP は主に、行列指向の数値プログラミングで発生するような、ループの反復回数がエントリ時に既知であるような、非常に規則的なループを並列化する方法を指定していました。これは制限として認識され、さまざまなタスク並列拡張機能が実装に追加されました。2005 年にタスク並列性を標準化する取り組みが結成され、Cilk、X10、Chapelのタスク並列性機能からヒントを得て、2007 年に提案が発表されました。[ 11 ]
バージョン 3.0 は 2008 年 5 月にリリースされました。3.0 の新機能には、タスクの概念とタスク構造が含まれており、[ 12 ] OpenMP 2.0 の大部分を占めていた並列ループ構造を超えて OpenMP の範囲が大幅に拡大されました。[ 13 ] OpenMP 3.1 は 2011 年 9 月にリリースされ、タスクの終了 (final 句) やタスクの依存関係 (depend 句) などの機能でタスク処理が強化されました。また、Fortran 2003 のサポートが改善され、アトミック構造の強化も追加されました。
仕様のバージョン 4.0 は 2013 年 7 月にリリースされました。[ 14 ]次の機能が追加または改善されています:アクセラレータのサポート、アトミック操作、エラー処理、スレッドアフィニティ、タスク拡張、ユーザー定義の削減、SIMDサポート、Fortran 2003サポート。[ 15 ] OpenMP 4.0 (2013) では、target ディレクティブを介してアクセラレータ (GPU など) のサポートも導入され、GPU や FPGA などのデバイスに計算をオフロードできるようになりました。また、ベクトル化とユーザー定義の削減のために SIMD (単一命令複数データ) ディレクティブも追加されました。バージョン 4.5 (2015) では、デバイス サポートが強化され、依存関係のあるタスクが改善され、最適化を向上させるためにループ変換が導入されました。
バージョン 5.0 (2018) では、アクセラレータのメモリ管理、ターゲット ディレクティブでのディープ コピーのサポート、ループ変換構造などの重要な機能が追加されました。また、MPI などの他のプログラミング モデルとの相互運用性も向上しました。OpenMP 5.1 (2020) では使いやすさに重点が置かれ、ループ融合、タスク削減の改善、C++11/14/17 構造のサポートの向上などの機能が追加されました。バージョン 5.2 (2021) では、既存の機能が改良され、最新のハードウェアとの互換性が向上し、記述的なループ ディレクティブのサポートが追加されました。[ 16 ]
バージョン6.0は2024年11月にリリースされました。[ 17 ]このバージョンは、異種コンピューティング向けのOpenMPの強化、AI/MLワークロードとの統合、多様なアーキテクチャ間でのパフォーマンス移植性の向上に重点を置いています。
最新バージョンのすべての機能をサポートしているコンパイラ(およびOS)は存在しないことに注意してください。

OpenMPの中核となる要素は、スレッド生成、ワークロード分散(ワークシェアリング)、データ環境管理、スレッド同期、ユーザーレベルのランタイムルーチン、および環境変数といった構成要素である。
C/C++では、OpenMPは#pragma sを使用します。OpenMP固有のプラグマは以下のとおりです。
このプラグマomp parallelは、構造体に含まれる処理を並列に実行するために、追加のスレッドを分岐するために使用されます。元のスレッドは、master threadスレッドID 0で表されます。
例(C言語プログラム):複数のスレッドを使用して「Hello, world.」を表示する。
#include <stdio.h> #include <omp.h>int main ( void ) { #pragma omp parallel printf ( "Hello, world. \n " ); return 0 ; }GCCを使用してコンパイルするには、フラグ-fopenmpを使用してください。
$ gcc -fopenmp hello.c -o hello -ldl 2つのコア、つまり2つのスレッドを持つコンピュータでの出力:
こんにちは世界。 こんにちは世界。 しかし、 2つのスレッドが標準出力を共有することによって発生する競合状態のため、出力が文字化けする可能性もあります。
こんにちは、こんにちは、世界。 rld。 アトミックかどうかは、printf基盤となる実装に依存します[ 18 ]。std::coutこれは、デフォルトでスレッドセーフであるC++11とは異なります[ 19 ]。
1つまたは複数のスレッドに独立した作業を割り当てる方法を指定するために使用されます。
omp forまたはomp do、ループの反復処理をスレッド間で分割するために使用され、ループ構造とも呼ばれます。sections連続するが独立したコードブロックを異なるスレッドに割り当てるsingle: 1つのスレッドのみで実行されるコードブロックを指定すると、最後にバリアが暗黙的に適用されます。master: single と同様ですが、コードブロックはマスター スレッドのみによって実行され、最後にバリアは暗黙的に適用されません。例:大規模配列の値を並列に初期化し、各スレッドで作業の一部を実行する。
// 配列の長さを表す定数としてNを定義する#define N 100000int main ( int argc , char * argv []) { int a [ N ];#pragma omp parallel for for ( int i = 0 ; i < N ; i ++ ) { a [ i ] = 2 * i ; }return 0 ; }この例は並列処理が容易にでき、 の値のみに依存しますi。 OpenMPparallel forフラグは、OpenMP システムにこのタスクをワーキング スレッド間で分割するように指示します。各スレッドは、変数の一意かつプライベートなバージョンを受け取ります。[ 20 ]例えば、2 つのワーカー スレッドがある場合、1 つのスレッドにはi0 から 49999 までのバージョンが渡され、もう 1 つのスレッドには 50000 から 99999 までのバージョンが渡される可能性があります。
バリアントディレクティブは、プログラマがパフォーマンスの移植性を向上させるためにOpenMP 5.0仕様で導入された主要な機能の1つです。これにより、コンパイル時にOpenMPプラグマとユーザーコードを適応させることができます。この仕様では、実装によって提供されるアクティブなOpenMP構造、実行デバイス、および機能を記述するための特性、特性とユーザー定義条件に基づくコンテキストセレクタ、およびmetadirectiveユーザーdeclare directiveが同じコード領域をバリアントディレクティブでプログラムするためのディレクティブが定義されています。
metadirectiveは実行可能なディレクティブであり、OpenMPの条件またはコンテキストを定義する特性に基づいて複数のディレクティブバリアントから選択することにより、コンパイル時に条件付きで別のディレクティブに解決されます。declare variantディレクティブは、と同様の機能を持つがmetadirective、呼び出し箇所でコンテキストまたはユーザー定義の条件に基づいて関数バリアントを選択する。2 つのバリアントディレクティブによって提供されるバリアント選択のメカニズムは、OpenMP でのバリアント選択を直接サポートし、OpenMP コンパイラがバリアントとコンテキストから最終的なディレクティブを分析および決定できるようにするため、C/C++ のプリプロセッシングよりも使いやすい。
// 配列の長さを表す定数として配列サイズを定義します: #define N 100000 // プリプロセスディレクティブを使用したコードの適応int v1 [ N ]; int v2 [ N ]; int v3 [ N ]; #if defined(nvptx) #pragma omp target teams distribute parallel for map(to:v1,v2) map(from:v3) for ( int i = 0 ; i < N ; ++ i ) { v3 [ i ] = v1 [ i ] * v2 [ i ]; } #else #pragma omp target parallel for map(to:v1,v2) map(from:v3) for ( int i = 0 ; i < N ; ++ i ) { v3 [ i ] = v1 [ i ] * v2 [ i ]; } #endif// OpenMP 5.0 のメタディレクティブを使用したコード適応int v1 [ N ]; int v2 [ N ]; int v3 [ N ]; #pragma omp target map(to:v1,v2) map(from:v3) #pragma omp metadirective \ when(device={arch(nvptx)}: target teams distribute parallel for) \ default(target parallel for) for ( int i = 0 ; i < N ; ++ i ) { v3 [ i ] = v1 [ i ] * v2 [ i ]; }OpenMPは共有メモリプログラミングモデルであるため、OpenMPコード内のほとんどの変数はデフォルトで全てのスレッドから参照可能です。しかし、競合状態を回避するためにプライベート変数が必要な場合や、シーケンシャル部分と並列領域(並列実行されるコードブロック)の間で値を渡す必要がある場合があるため、データ環境管理は、 OpenMPディレクティブにデータ共有属性句を追加することで導入されます。属性句の種類は以下のとおりです。
shared並列領域の外で宣言されたデータは共有され、すべてのスレッドから同時に参照およびアクセス可能になります。デフォルトでは、ループ反復カウンタを除く、ワークシェアリング領域内のすべての変数が共有されます。private並列領域内で宣言されたデータは各スレッドに固有のものであり、各スレッドはローカルコピーを保持し、それを一時変数として使用します。プライベート変数は初期化されず、並列領域外で使用するために値が保持されることもありません。デフォルトでは、OpenMP ループ構造内のループ反復カウンタはプライベートです。default: を使用すると、プログラマは並列領域内のデフォルトのデータスコープをshared、noneC/C++ の場合は 、 またはshared、Fortran の場合は 、 firstprivate、private、 またはのいずれかに指定できます。このオプションでは、プログラマは並列領域内の各変数をデータ共有属性句を使用して宣言する必要があります。nonenonefirstprivateデータは各スレッドに固有のものですが、マスタースレッドから同じ名前の変数の値を使用して初期化されます。lastprivate: データは各スレッドにプライベートです。現在のイテレーションが並列化されたループの最後のイテレーションである場合、このプライベートデータの値は並列領域外の同じ名前のグローバル変数にコピーされます。変数は とfirstprivateの両方になりますlastprivate。threadprivateデータはグローバルデータですが、実行時には各並列領域内でプライベートになります。threadprivateとの違いは、privateに関連付けられたグローバルスコープthreadprivateと、並列領域間で保持される値です。critical: 囲まれたコードブロックは、一度に 1 つのスレッドによってのみ実行され、複数のスレッドによって同時に実行されることはありません。これは、共有データを競合状態から保護するためによく使用されます。atomic次の命令のメモリ更新(書き込み、または読み出し-変更-書き込み)はアトミックに実行されます。ステートメント全体がアトミックになるわけではなく、メモリ更新のみがアトミックになります。コンパイラは、を使用する場合よりもパフォーマンスを向上させるために特別なハードウェア命令を使用する場合がありますcritical。ordered: 構造化ブロックは、逐次ループで反復処理が実行される順序で実行されます。barrier各スレッドは、チーム内の他のすべてのスレッドがこの段階に到達するまで待機します。ワークシェアリング構造では、最後に暗黙的なバリア同期が行われます。nowait: 割り当てられた作業を完了したスレッドは、チーム内のすべてのスレッドの完了を待たずに処理を続行できることを指定します。この句がない場合、スレッドは作業共有構造の終了時にバリア同期に遭遇します。schedule (type, chunk)これは、ワークシェアリング構造が do ループまたは for ループである場合に役立ちます。ワークシェアリング構造内の反復処理は、この句で定義されたスケジューリング方法に従ってスレッドに割り当てられます。スケジューリングには次の 3 つのタイプがあります。 staticここでは、すべてのスレッドがループの反復処理を実行する前に、反復処理回数が割り当てられます。デフォルトでは、反復処理回数はスレッド間で均等に分割されます。ただし、パラメータに整数を指定すると、chunk特定のスレッドに連続した反復処理のチャンク数が割り当てられます。dynamicここでは、一部の反復処理が少数のスレッドに割り当てられます。特定のスレッドが割り当てられた反復処理を完了すると、残りの反復処理から次の反復処理を取得します。このパラメータは、chunk一度にスレッドに割り当てられる連続した反復処理の数を定義します。guided連続した反復処理の大きなチャンクが、各スレッドに動的に割り当てられます(上記のとおり)。チャンクサイズは、パラメータで指定された最小サイズまで、割り当てが繰り返されるたびに指数関数的に減少します。chunkif: これは、条件が満たされた場合にのみ、スレッドがタスクを並列化するようにします。そうでない場合は、コードブロックは逐次的に実行されます。copyinfirstprivate変数と同様にprivate、対応するグローバル変数から値を渡すthreadprivate場合を除き、変数は初期化されません。変数の値はプログラム全体の実行を通して維持されるため、初期化は不要です。copyincopyoutthreadprivatecopyprivate: 1 つのスレッド (スレッド) 上のプライベート オブジェクトから、チーム内の他のスレッド上の対応するオブジェクトへのsingleデータ値のコピーをサポートするために使用されます。singlereduction (operator | intrinsic : list): 変数は各スレッドにローカルコピーを持ちますが、ローカルコピーの値はグローバル共有変数に集約(縮約)されます。これは、operator変数に対する特定の操作(この特定の句で指定)が反復的に実行され、特定の反復における値が前の反復における値に依存する場合に非常に便利です。操作の増分に至るまでのステップは並列化されますが、スレッドはグローバル変数をスレッドセーフな方法で更新します。これは、一般的な例として、関数や微分方程式の数値積分を並列化する場合に必要になります。flushこの変数の値は、並列処理の外部でこの値を使用するために、レジスタからメモリに復元されます。master: マスター スレッド (OpenMP ディレクティブの実行中に他のすべてのスレッドをフォークしたスレッド) によってのみ実行されます。暗黙のバリアはありません。他のチーム メンバー (スレッド) が到達する必要はありません。スレッド数の変更/確認、実行コンテキストが並列領域にあるかどうかの検出、現在のシステム内のプロセッサ数、ロックの設定/解除、タイミング関数などに使用されます。
OpenMPアプリケーションの実行機能を変更する方法。ループの反復スケジューリング、デフォルトのスレッド数などを制御するために使用されます。たとえば、OMP_NUM_THREADSアプリケーションのスレッド数を指定するために使用されます。
OpenMP は多くの商用コンパイラに実装されています。たとえば、Visual C++ 2005、2008、2010、2012、2013 は OpenMP 2.0 (Professional、Team System、Premium、Ultimate エディション[ 21 ] [ 22 ] [ 23 ] ) をサポートしており、さまざまなプロセッサ向けのIntel Parallel Studioもサポートしています。[ 24 ] Oracle Solaris Studioコンパイラとツールは、Solaris OS (UltraSPARC および x86/x64) および Linux プラットフォーム向けの生産性向上機能を備えた最新の OpenMP 仕様をサポートしています。The Portland Groupの Fortran、C、C++ コンパイラも OpenMP 2.5 をサポートしています。GCCもバージョン 4.2 以降 OpenMP をサポートしています。
OpenMP 3.0を実装したコンパイラ:
いくつかのコンパイラがOpenMP 3.1をサポートしています。
OpenMP 4.0をサポートするコンパイラ:
OpenMP 4.5をサポートするコンパイラがいくつかある。
OpenMP 5.0に対する部分的なサポート:
OpenMPディレクティブで注釈付けされたソースコードを生成する自動並列化コンパイラ:
いくつかのプロファイラとデバッガは、OpenMPを明示的にサポートしています。
長所:
短所:
OpenMPを使用して並列化されたプログラムをN個のプロセッサプラットフォームで実行した場合、 N倍の高速化が期待されるかもしれない。しかし、以下の理由から、これはめったに起こらない。
一部のベンダーは、 OpenMP スレッドにプロセッサ アフィニティを設定して、特定のプロセッサ コアに関連付けることを推奨しています。[ 46 ] [ 47 ] [ 48 ] これにより、コア間のスレッド移行とコンテキスト スイッチのコストが最小限に抑えられます。また、データの局所性が向上し、コア (またはプロセッサ) 間のキャッシュ コヒーレンシ トラフィックが減少します。
OpenMPの利用方法を実証し、その性能をテストし、正確性を評価するために、さまざまなベンチマークが開発されてきた。
簡単な例
パフォーマンスベンチマークには以下が含まれます。
正確性のベンチマークには以下が含まれます。