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

OpenMPはマルチスレッドの実装です。マルチスレッドとは、プライマリスレッド(連続して実行される一連の命令)が指定された数のサブスレッドをフォークし、システムがそれらのサブスレッド間でタスクを分割する並列化手法です。その後、各スレッドは並行して実行され、ランタイム環境によってスレッドが複数のプロセッサに割り当てられます。
並列実行を意図したコードセクションは、そのセクションが実行される前にスレッドを形成するコンパイラディレクティブによって適切にマークされます。[3]各スレッドにはIDが付与されており、これは関数( )を使用して取得できますomp_get_thread_num()。スレッドIDは整数で、プライマリスレッドのIDは0です。並列化されたコードの実行後、スレッドはプライマリスレッドに戻り、プログラムの最後まで継続されます。
デフォルトでは、各スレッドは並列化されたコード部分を独立して実行します。ワークシェアリング構造を使用することで、タスクをスレッド間で分割し、各スレッドが割り当てられたコード部分を実行することができます。このようにして、OpenMP ではタスク並列性とデータ並列性の両方を実現できます。
ランタイム環境は、使用状況、マシン負荷、その他の要因に応じてプロセッサにスレッドを割り当てます。ランタイム環境は環境変数に基づいてスレッド数を割り当てることも、コード内で関数を使用して割り当てることもできます。OpenMP関数は、C / C++ではomp.hというヘッダーファイルに含まれています。
OpenMP Architecture Review Board (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年にはタスク並列性の標準化に向けた取り組みが開始され、2007年にはCilk、X10、Chapelのタスク並列機能に着想を得た提案が公開されました。[11]
バージョン3.0は2008年5月にリリースされました。3.0の新機能には、タスクの概念とtask構文[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)では、ターゲットディレクティブを介したアクセラレータ(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]。C ++11のstd::coutはデフォルトでスレッドセーフです[19]。
独立した作業を 1 つまたはすべてのスレッドに割り当てる方法を指定するために使用されます。
omp forまたはomp do:ループ反復をスレッド間で分割するために使用されます。ループ構造とも呼ばれます。sections: 連続しているが独立したコードブロックを異なるスレッドに割り当てるsingle: 1つのスレッドのみで実行されるコードブロックを指定すると、最後にバリアが暗黙的に存在しますmaster: single に似ていますが、コード ブロックはマスター スレッドによってのみ実行され、最後にバリアは暗黙的に適用されません。例: 各スレッドで作業の一部を実行し、大きな配列の値を並列に初期化する
// N を配列の長さを表す定数として定義します
#define N 100000
int main ( int argc , char * argv []) { int a [ N ];
#pragma omp parallel for
for ( int i = 0 ; i < N ; i ++ ) { a [ i ] = 2 * i ; }
0を返す; }
この例は驚くほど並列であり、 の値のみに依存しますi。OpenMPparallel forフラグは、OpenMPシステムにこのタスクを複数の作業スレッドに分割するよう指示します。各スレッドは、変数の一意かつプライベートなバージョンを受け取ります。[20]例えば、2つの作業スレッドがある場合、一方のスレッドにはi0から49999まで実行される のバージョンが渡され、もう一方のスレッドには50000から99999まで実行される のバージョンが渡される可能性があります。
バリアントディレクティブは、プログラマがパフォーマンスの移植性を向上させるためにOpenMP 5.0仕様で導入された主要な機能の一つです。これにより、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 メタディレクティブ \ 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++の場合は 、 、または、 Fortranの場合はshared、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: これにより、条件が満たされた場合にのみスレッドがタスクを並列化します。条件が満たされない場合は、コードブロックはシリアルに実行されます。copyin:変数firstprivateの場合と同様に、対応するグローバル変数から値を渡すためにを使用しない限り、変数は初期化されません。変数の値はプログラム全体の実行を通じて維持されるため、 は必要ありません。privatethreadprivatecopyincopyoutthreadprivatecopyprivate: 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 ] などがOpenMPをサポートしています。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 の使用を実証し、そのパフォーマンスをテストし、正確性を評価するために、さまざまなベンチマークが開発されています。
簡単な例
パフォーマンス ベンチマークには次のものが含まれます。
正確性のベンチマークには次のものが含まれます。