OpenMPに関して
OpenMPとは†
OpenMPは簡単に従来のプログラムを並列化することができるライブラリで以下のような特徴を持っています.
- シンプルなプログラミングモデル - プラグマによる並列計算部分の指定
- コンパイラのサポート - インテルコンパイラ,Visual C++ 2005, gcc(v4.2以降)
- 段階的な並列化の適用 - プログラム全体の設計時から並列化を考える必要がない
- 互換性 - Win/Linux, コア数の増大には環境変数の変更だけで対応可
Visual Studioでの設定†
Visual C++ 2005でOpenMPを用いるためには,コンパイラオプションに "/openmp" をセットします.
プロジェクトのプロパティから,
構成プロパティ → C/C++ → 言語 の OpenMPサポート
を「はい」にします.
OpenMP使用時にはomp.hをインクルードする.
#include <omp.h>
OpenMP†
有効/無効の確認†
_OPENMPが定義されていれば,OpenMPが有効にしてコンパイルされている.
1
2
3
| | #ifdef _OPENMP
...
#endif
|
スレッド数の確認†
omp_get_max_threads()で最大スレッド数を確認できる.
1
2
3
| | #ifdef _OPENMP
cout << "OpenMP : On, threads =" << omp_get_max_threads() << endl;
#endif
|
スレッド番号の取得†
int omp_get_thread_num(void);
スレッド数の変更†
forの並列化†
1
2
3
4
| | #pragma omp parallel for
for(int i = 0; i < n; ++i){
}
|
連続するfor文なら
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| | #pragma omp parallel
{
#pragma omp for
for(int i = 0; i < n; ++i){
}
#pragma omp for
for(int i = 0; i < n; ++i){
}
#pragma omp for
for(int i = 0; i < n; ++i){
}
}
|
parallelでスレッドの生成が行われるので,
parallel指示は少ない方がよい.
セクションでの並列化†
1
2
3
4
5
6
7
8
9
10
11
| | #pragma omp parallel sections num_threads(2)
{
#pragma omp section
{
printf("thread %d\n", omp_get_thread_num());
}
#pragma omp section
{
printf("thread %d\n", omp_get_thread_num());
}
}
|
reduction†
合計値を求めるなどのreduction演算の場合は,
1
2
3
4
| | #pragma omp for reduction(+:s)
for(int i = 0; i < n; ++i){
s += i;
}
|
変数のスコープ†
OpenMPのブロック内の変数はデフォルトでは全てのスレッドで共有されます.
しかし,例えばループ内で用いる一時変数などが共有されると,別のスレッドがその値を書き換えてしまうことがあります.
そのため,スレッドごとに独立な変数とするためにprivateを用います.
1
2
3
4
5
6
| | double x;
#pragma omp parallel for private(x)
for(long i = 0; i < n; ++i){
}
|
変数が複数あるときは,private(x,y,z)のように","で区切って指定します.
変数のスコープを変更するための命令としては,
共有メモリでスレッド間でデータをやり取りする場合,他のスレッドの処理を待つ必要があります.
これにはバリア同期を用いることができます.同期したい位置に以下を指定します.
#pragma omp barrier
全スレッドがバリアに到達するまで,スレッドは待機します.
また,スレッド間のメモリアクセスの一貫性をとるために,
#pragma omp flush
があります.ただし,barrierやcriticalなどの前後では明示的に指定しなくても
暗黙にflushされます.
共有メモリの資源を占有させるためのロックや,それを解放するためのアンロックもサポートされています.
ロックを用いる場合は,omp_lock_t型のロック変数を用います.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| | omp_lock_t lock0;
omp_init_lock(&lock0);
#pragma omp parallel num_threads(4)
{
int tid = omp_get_thread_num();
omp_set_lock(&lock0);
for(int i = 0; i < 3; ++i){
cout << "Thread " << tid << endl;
}
omp_unset_lock(&lock0);
}
omp_destroy_lock(&lock0);
|
上記コードの実行結果は,
Thread 0
Thread 0
Thread 0
Thread 1
Thread 1
Thread 1
Thread 2
Thread 2
Thread 2
Thread 3
Thread 3
Thread 3
計算例†
円周率の計算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| | #include <stdio.h>
#include <windows.h>
#include <omp.h>
volatile DWORD g_dwStart;
double CalPI(long n)
{
double x, pi, sum = 0.0, step;
step = 1.0/(double)n;
#pragma omp parallel for reduction(+:sum) private(x)
for(long i = 1; i <= n; ++i){
x = (i-0.5)*step;
sum = sum+4.0/(1.0+x*x);
}
pi = step*sum;
return pi;
}
int main(void)
{
#ifdef _OPENMP
printf("OpenMP : On, threads = %d\n", omp_get_max_threads());
#endif
long n = 100000000;
for(int i = 0; i < 3; ++i){
g_dwStart = GetTickCount();
double pi = CalPI(n);
printf("pi = %.15f, %d [msec]\n", pi, GetTickCount()-g_dwStart);
}
return 0;
}
|
リンク†