AND OR  

OpenMP(Wikipedia)とは、
並列コンピューティング環境を利用するために用いられる標準化された基盤(API)です。

このページでは OpenMP C/C++ API 2.0 を対象としています。

OpenMP - 並列処理の留意点 (別ページ)

導入編

gcc(gcc) の場合

gccはバージョン4.2からOpenMPに正式対応しました。
コンパイルオプションに -fopenmp を付けてコンパイルします。

Visual Studio(cl) の場合

Visual Studio 2005 ProからOpenMPがサポートされたことにより、手軽に並列処理のコードを書くことが可能になりました。
ここではVisual Studio 2008 Proでの設定方法を示します。

メニューから

["プロジェクト名"のプロパティ] → [構成プロパティ] → [C/C++] → [言語] → [OpenMPサポート]

を "はい" にするだけです。 (コンパイルオプションは /openmp)

Intelコンパイラ(icc) の場合

コンパイルオプションに -openmpを付けてコンパイルします。

基本編

OpenMPでのプログラミングでは、次の5つのカテゴリに分類される構文と実行時関数および環境変数について理解することが必要です。

・並列実行領域(Parallel Regions)構文
・ワークシェアリング(Work Sharing)構文
・データ環境(Data Environment)構文
・同期(Synchronization)構文
・実行時関数/環境変数(Runtime functions/environment variables)

OpenMPでは、ディレクティブと呼ばれるコンパイラへの命令文を記述します。
並列で処理したい所を、

#pragma ・・・ ・・・

で指定します.
OpenMPをサポートしていない場合やコンパイルオプションで無効にした場合はこれらは単にコメント行とみなされます。

並列実行領域(Parallel Regions)構文

parallel 指示文

複数のスレッドによって並列処理される並列領域を定義します。
この指示文以降のブロックをマルチスレッドで実行するようになります。

[例]

#pragma omp parallel num_threads(スレッド数)
{
 /* ここの内容が全スレッドで実行される */
}

※なお、「num_threads(スレッド数)」部分を省略すると、コアの数だけスレッドを生成します。
・デュアルコア → 2スレッド
・クアッドコア → 4スレッド

  • Hello World in OpenMP (スレッドの数だけHello World)
  • 出力例(デュアルコアの場合)
    Hello World from 0
    Hello World from 1

※これ以降はいずれもデュアルコアでの出力例とします。
また、omp_get_thread_num()は実行時ライブラリ関数です。 (詳細は後述)

  • Pthreads を利用した場合
    Posixスレッドを利用して同様の処理を書くとOpenMPの手軽さがよくわかります。
    (エラー処理は省略しているので注意)

ワークシェアリング(Work Sharing)構文

for 指示文

並行領域内のforループ内で行われる動作をスレッド間に分割します。

  • int型の配列a[100]の全要素を0に初期化するプログラムを並列化します。

実行スレッド数を4にして実行すると、4つのスレッドが下記のようにforループの処理を分担して実行します。

スレッド0番: for(i= 0;i< 25;i++) a[i]=0;
スレッド1番: for(i=25;i< 50;i++) a[i]=0;
スレッド2番: for(i=50;i< 75;i++) a[i]=0;
スレッド3番: for(i=75;i<100;i++) a[i]=0;

また、forの後ろに { を付けてしまうと、
「error C3014: OpenMP 'parallel for' ディレクティブの後に for ループが必要です」
と怒られるので 'parallel for' の後には { を入れないようにしてください。

sections 指示文

各スレッドで別々の処理を行う場合はsections指示文で指定します。

  • Hello Worldプログラムを並列で実行
  • 出力例
    Hello, from 1 of 3
    World, from 2 of 3
    OpenMP, from 0 of 3
    OpenMP, from 0 of 3

「#pragma omp sections」指示文で指定したブロックの出口では全てのスレッドがその場所に到達するまで待機しているため、ブロック以降の処理を開始する段階で全てのセクションの実行が終えていることが保証されています。ただし、「#pragma omp sections」指示文にnowait指示節が指定されていない場合に限ります。nowaitを指定したい場合は、ショートカット構文「#pragma omp parallel sections」を使わずに、parallelとsectionsブロックは分けて書き、そのsectionsブロックにnowaitを指定します。

single 指示文

「#pragma omp parallel」で並列処理中に、1つのスレッドだけで実行したい処理はsingle構文を指定します(どのスレッドが実行するかは決まっていない)。並列実行領域の中でファイルIOを行うような場合に利用します。

  • 出力例
    single
    parallel
    parallel

master 指示文

マスタースレッド(0番スレッド)のみが処理を実行するように指定します。

  • 出力例
    Hello, from 0 of 2
    Master Hello, from 0 of 2
    Hello, from 0 of 2
    Hello, from 1 of 2
    Hello, from 1 of 2

一般には、マスタースレッドに処理を限定するよりは「#pragma omp single」指示文を使ってどのスレッドが実行してもいいようにした方が効率は良くなります。

データ環境(Data Environment)構文

private、shared 指示節

各スレッドにデータを共有させたり(共有変数)、逆に独自のデータ(プライベート変数)を持つように指定します。共有変数にはshared、プライベート変数にはprivate指示節を用います。何も指定しない場合は基本的に共有変数となるので, shared指示節を明示的に記述することはあまりしません。 一方、private指示節はループ処理などでよく使われます。

次のディレクティブに指定可能
・for
・parallel
・sections
・single (shared指示節は指定不可)

  • 二重ループの並列化例

二重ループの外側のループを並列化した場合、何も指示しなければ内側のループインデックスは共有変数となります。並列実行時に内側のループインデックスが共有変数のままだと各スレッドが互いにそれを上書きし合って内側のループを正しく実行することができないため、これをプライベート変数として宣言する必要があります。

reduction 指示節

for構文におけるリダクション演算を指示します。ここでは演算子と変数のリストを指示することになります。変数は共有データである必要がありますが、並列実行領域内ではこれらの変数は各スレッド毎にプライベートデータとして処理され、for構文終了時の同期処理実行後にリダクション演算の結果が確保されます。指示可能な演算子は +, *, -, &, ^, |, &&, || などです。代表的なリダクション演算は、総和演算の他に最大値や最小値を求める演算などが挙げられます。

次のディレクティブに指定可能
・for
・parallel
・sections

  • for構文におけるリダクション演算 (総和を求める)

同期(Synchronization)構文

マルチスレッドで複数処理を実行する場合、データへのアクセス同期が必要となります。マルチスレッドアプリケーションでは不正なデータアクセスが必ずしも同じ結果を招くとは限らず、データ競合が潜在しているにもかかわらず問題が表に出ないこともあるので十分に注意しなければなりません。

atomic 指示節

「読み込み+更新+書き込み」が一連の操作(アトミック操作)で行われるデータの同期に利用します。

atomic指示節は複数行を囲むことができません。

barrier 指示節

OpenMPで生成されるスレッドはOSのスケジューラで管理されるので、スレッドの実行順序や実行のタイミングは不定です。並列領域内でbarrier指示節が指定されると、処理中の各スレッドはいったんbarrier指示節の場所で待機します。全てのスレッドがbarrier指示節に到達するとスレッドは処理を再開します。parallel構文内で複数のスレッド制御が行われ、前の処理が完了しなければ次の処理に移れない場合に利用します。

  • 暗黙の同期
    いくつかの指示文(parallel, single, for, sections)では明示的にbarrier指示節を指定する必要はありません。

その他

if 指示節

forループを並列化する場合、ループ回数が大きくなければ並列化の恩恵が受けられないので、ループ回数が一定数以上の時のみ並列実行させるようにすると良いです。

また、一定数のスレッドが利用可能な場合のみ並列実行させるというアプローチもあります。

nowait 指示節

スレッド間でバリア同期を行わないことを指示します。冗長・不要なバリアを取り除くことでパフォーマンスが向上しますが、reduction指示節を伴う箇所で指定すると、リダクション変数のマージで競合が発生することがあるので注意。
次のディレクティブに指定可能
・for
・sections
・single

ただし「#pragma omp parallel sections」のようなショートカット構文には指定できないので注意!!

実行時関数/環境変数

(Runtime functions/environment variables)
OpenMPの実行時ライブラリ関数は, 通常の関数と同様にプログラム中で呼び出すことが出来ます。この実行時ライブラリ関数をC/C++のプログラムで用いる場合、omp.hを読み込む必要があります。

環境系関数

  • void omp_set_num_threads(int nthreads)
    並列領域で使用できるスレッド数を設定します。
    OMP_NUM_THREADS で指定されている場合はこのAPIで上書きされます。
  • int omp_get_num_threads(void)
    並行領域を実行するチーム内の現在のスレッド数を返します。
  • int omp_get_thread_num(void)
    そのチーム内でのスレッド番号を返します。
  • int omp_in_parallel(void)
    並列領域内か否かを判定します。真(1)、偽(0)