OpenMP

複数のCPUを持った計算機で簡単に並列化を実現するためのもの、らしいです。 gcc のバージョン 4.2 から対応しているようです。 やってみたところ、gcc だとコンパイルできなくて、 g++ をインストールすると付いてくる c++ だとコンパイルできました。

コンパイラのバージョン

コンパイラのバージョンは -v オプションを付けると出てきます。
% c++ -v

Hello World

例によって、Hello World をやってみます。
hello.C
#include <omp.h>
#include <stdio.h>
int main(void){
#pragma omp parallel
  {
    #ifdef _OPENMP
    printf("Hello World from %d of %d\n",
	   omp_get_thread_num(), omp_get_num_threads());
    #else
    printf("No OpenMP\n");
    #endif
  }
  return 0;
}
赤字で書いたところが並列化を指定しています。 このような指定文の行には括弧を付けてはならないようです。
赤字の指定文を
...
#pragma omp parallel num_threads(スレッド数)
...
とすると、指定したスレッド数で並列化が行われます。 num_threads()を省略すると、コアの数だけのスレッドを使います。

#ifdef _OPENMP
#else
#endif
の3行は、OpenMPを使ってコンパイルしているか否かによって ソースを変えています。 omp_get_thread_num()などの関数は OpenMP を使ってコンパイルしないといけないようなので、 並列化しない場合でもエラーなくコンパイルするための処置です。 なお、omp.h はこれらの関数の定義ファイルです。

ソースをコンパイルします。

% c++ -fopenmp hello.C
% ./a.out
Hello World from X of Y
....
X には CPUの番号が、Y にはCPU(あるいはスレッド)の数が入っているはずです。 このような行が X の値を変えながら Y 行出力されるはずです。

オプション -fopenmp を外すと、通常のコンパイルを行います。

% c++ hello.C
% ./a.out
No OpenMP

Reduction

多数のデータを一つのデータに集約するタイプの計算を reduction と言うそうです。 ベクトル成分の総和を取るとか、最大値を取るとか、そういう演算です。 このような操作には、reduction 用の指定があります。
sum.C
#include 
#include 
int main(){
  int i,A[1000],s;
  s = 0;
#ifdef _OPENMP
#pragma omp parallel for
#endif
    for(i=0; i<1000; i++)
      A[i] = i;
#ifdef _OPENMP
#pragma omp parallel for reduction(+:s)
#endif
    for(i=0; i<1000; i++)
      s += A[i];
  printf("sum = %d\n", s);
}
#pragma omp parallel for はベクトルの初期化を並列化することを指定します。
for で回す番号を各スレッドに割り振ります。
#pragma omp parallel for reduction(+:s) は、変数 s を使って総和を計算することを指定します。

二つ現われる parallel をまとめて一つにすることもできます。

#include 
#include 
int main(){
  int i,A[1000],s;
  s = 0;
#ifdef _OPENMP
#pragma omp parallel
#endif
  {
#ifdef _OPENMP
#pragma omp for
#endif
    for(i=0; i<1000; i++)
      A[i] = i;
#ifdef _OPENMP
#pragma omp for reduction(+:s)
#endif
    for(i=0; i<1000; i++)
      s += A[i];
  }
  printf("sum = %d\n", s);
}
こうすると、並列化にともなうオーバーヘッドが減らせる、ということらしいです。
#pragma omp parallel で指定する領域は括弧{}で囲いますが、 #pragma omp for や #pragma omp for reduction の後ろは 括弧{}で括ってはいけません。

二重ループ

for が二重になっていて外側を並列化する場合には、 内側の for を回す変数に注意しないといけません。
#pragma omp parallel for private(j)
for(i=0; i<100; i++){
  for(j=0; j<100; j++){
  ....
  }
}
外側のループ for(i) が並列化されると、内側の変数 j は共有変数となります。 i の各ループで j が共有されるので、お互いが j を上書きしてしまって 正しい計算ができなくなります。 これを防ぐため、各ループでは j はプライベートであることを明示的に宣言します。

同期

計算を正しくするためには、すべてスレッドの計算結果を待って 次の計算に移らなければならない場面が出てくるでしょう。 そのような場合には、スレッド間の同期を取ります。

barrier

#pragma barrier
と書くと、各スレッドは全スレッドの処理がここまで到達するまで待機します。

atomic

#pragma omp atomic
と書くと、この下1行の「読み込み、更新、書き込み」が一連の操作で行われます。 atomic 指定の下には複数行を括弧{}で囲むことはできません。

シングルタスク

参考サイト


Last modified: Mon Feb 13 18:47:22 JST 2012