ホームに戻る
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++){
}
}