ホームに戻る
 OpenMP メモ

OpemMP はマルチコアCPUで処理を並列化できる。
インテルコンパイラ、Visual C++、GCC などで使用可能。

今回は Visual C++ 2010 Express を使用する。

コマンドラインで使用できるように、
コマンドラインより以下を実行しておく。

(略)\Microsoft Visual Studio 10.0\VC\bin\vcvars32

これで cl コマンドでコンパイルできる。

次に、

Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1

が必要なのでインストールする。

include\omp.h、include\ompassem.h
lib\vcomp.lib、lib\vcompd.lib
lib\amd64\vcomp.lib、lib\amd64\vcompd.lib

以上の6つのファイルが必要なので、
この構成で VC++2010 にコピー。

OpenMP を使ったコンパイルは以下のように行う。

cl /openmp test.cpp

以下は OpenMP によって for ループを並列化しています。

/*
*  OpenMP のサンプル
*/

#include <stdio.h>
#include <windows.h>
#include <omp.h>

void test_func(){
  #ifdef _OPENMP
  #pragma omp parallel for
  #endif
  for(int i = 0; i < 100000000; i++){

  }
}

int main(){
  LARGE_INTEGER freq, start, end;

  printf("max_threads:%d\n", omp_get_max_threads());

  if(QueryPerformanceFrequency(&freq)){
    double unit = 1000;

    QueryPerformanceCounter(&start);
    test_func();
    QueryPerformanceCounter(&end);

    unit = unit / freq.QuadPart;

    printf("%lfms\n", (double)((end.QuadPart - start.QuadPart) * unit));
  }

  return 0;
}

以上の実行結果は、以下のようになりました。

OpenMPなし:276.559718ms
OpenMPあり: 84.632214ms

なお、PCはAMDの8コアCPUを使用しました。

スレッドの割り当てを指定することもできます。
次のようにすると4つのスレッドで、

#pragma omp parallel for schedule(static,100) num_threads(4)
for(int i = 0; i < 800 ;++i){

}

以下のように並列処理を振り分けます。

スレッド0:0〜99
スレッド1:100〜199
スレッド2:200〜299
スレッド3:300〜399
スレッド0:400〜499
スレッド1:500〜599
スレッド2:600〜699
スレッド3:700〜799

static ではなく dynamic とすると、
暇なスレッドに処理を振り分けるようになります。

#pragma omp parallel for schedule(dynamic,100) num_threads(4)
for(int i = 0; i < 800 ;++i){

}

次のようにブロックごとに並列処理することもできる。

/*
*  OpenMP のサンプル
*/

#include <stdio.h>
#include <windows.h>
#include <omp.h>

void test_func(){
  #pragma omp parallel
  #pragma omp sections
  {
    // 1
    #pragma omp section
    {
      for(int i = 0; i < 100000000; i++);
    }
    // 2
    #pragma omp section
    {
      for(int i = 0; i < 100000000; i++);
    }
    // 3
    #pragma omp section
    {
      for(int i = 0; i < 100000000; i++);
    }
    // 4
    #pragma omp section
    {
      for(int i = 0; i < 100000000; i++);
    }
  }
}

int main(){
  LARGE_INTEGER freq, start, end;

  printf("max_threads:%d\n", omp_get_max_threads());

  if(QueryPerformanceFrequency(&freq)){
    double unit = 1000;

    QueryPerformanceCounter(&start);
    test_func();
    QueryPerformanceCounter(&end);

    unit = unit / freq.QuadPart;

    printf("%lfms\n", (double)((end.QuadPart - start.QuadPart) * unit));
  }

  return 0;
}

以上は4ブロックで並列処理を行っている。

ブロック数ごとの実行結果は以下のようであった。

 1:270.910768ms
 2:272.671557ms
 3:271.070114ms
 4:268.297946ms
 5:293.106923ms
 6:337.563942ms
 7:333.324307ms
 8:375.151400ms
 9:550.640022ms
10:542.512795ms

4ブロックまでは遅れず、
8ブロックになるにつれ遅れ始め、
9ブロックからガクンと遅くなり、
10ブロックは9ブロックとほとんど変わらない。

この結果でおおよその挙動が理解できるように思う。

 変数の扱いの注意

並列化で注意する必要があるのは、
スレッド間で変数を共有するかしないかである。

共有する場合は複数スレッドから同時に書き込む場合に、
意図しない動作をする場合がある。

例えば以下の例では内側のループの変数 x は共有される。

#pragma omp parallel for 
for(int y = 0; y < 100; y++){
  for(int x = 0; x < 100; x++){

  }
}

これは次のように書き換えれば共有されない。

#pragma omp parallel private(x, y)
for(int y = 0; y < 100; y++){
  for(int x = 0; x < 100; x++){

  }
}

逆に次のようにすれば強制的に共有する。

#pragma omp parallel shared(x, y)
for(int y = 0; y < 100; y++){
  for(int x = 0; x < 100; x++){

  }
}

inserted by FC2 system