OpenMPに関して



OpenMPとは

OpenMPは簡単に従来のプログラムを並列化することができるライブラリで以下のような特徴を持っています.

  • シンプルなプログラミングモデル - プラグマによる並列計算部分の指定
  • コンパイラのサポート - インテルコンパイラ,Visual C++ 2005, gcc(v4.2以降)
  • 段階的な並列化の適用 - プログラム全体の設計時から並列化を考える必要がない
  • 互換性 - Win/Linux, コア数の増大には環境変数の変更だけで対応可

Visual Studioでの設定

Visual C++ 2005でOpenMPを用いるためには,コンパイラオプションに "/openmp" をセットします. プロジェクトのプロパティから,

構成プロパティ → C/C++ → 言語 の OpenMPサポート

を「はい」にします.

openmp_vc2005.jpg

OpenMP使用時にはomp.hをインクルードする.

#include <omp.h>

OpenMP

有効/無効の確認

_OPENMPが定義されていれば,OpenMPが有効にしてコンパイルされている.

#ifdef _OPENMP
 ...
#endif

スレッド数の確認

omp_get_max_threads()で最大スレッド数を確認できる.

#ifdef _OPENMP
	cout << "OpenMP : On, threads =" << omp_get_max_threads() << endl;
#endif

スレッド番号の取得

int omp_get_thread_num(void);

スレッド数の変更

  • omp_set_num_threads()関数を使用する.
  • 環境変数 OMP_NUM_THREADSで指定する.
  • num_threads()でスレッド生成時に指定する.
    #pragma omp parallel num_threads(4)

forの並列化

#pragma omp parallel for
for(int i = 0; i < n; ++i){
	// 処理
}

連続するfor文なら

#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指示は少ない方がよい.

セクションでの並列化

#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演算の場合は,

#pragma omp for reduction(+:s)
for(int i = 0; i < n; ++i){
	s += i;
}

変数のスコープ

OpenMPのブロック内の変数はデフォルトでは全てのスレッドで共有されます. しかし,例えばループ内で用いる一時変数などが共有されると,別のスレッドがその値を書き換えてしまうことがあります. そのため,スレッドごとに独立な変数とするためにprivateを用います.

double x;

#pragma omp parallel for private(x)
for(long i = 0; i < n; ++i){
	// xを使った処理
}

変数が複数あるときは,private(x,y,z)のように","で区切って指定します. 変数のスコープを変更するための命令としては,

  • shared(変数リスト) : スレッド間で指定した変数を共有する
  • private(変数リスト) : スレッド間で指定した変数を共有しない
  • firstprivate(変数リスト) : privateと同様.ただし,並列処理構造前の値で初期化される.
  • lastprivate(変数リスト) : privateと同様.ただし,最後のループもしくは最後のsectionの値で更新される
  • copyprivate(変数リスト) : single指示で使用.変数が全てのスレッドで共有される また,グローバル変数やローカル静的変数をスレッドに対してプライベートにするには,
  • threadprivate(変数リスト) を用いて,
    #pragma omp threadprivate(g_x, g_y)
    などとグローバルで指定してやります.

同期

共有メモリでスレッド間でデータをやり取りする場合,他のスレッドの処理を待つ必要があります. これにはバリア同期を用いることができます.同期したい位置に以下を指定します.

#pragma omp barrier

全スレッドがバリアに到達するまで,スレッドは待機します.

また,スレッド間のメモリアクセスの一貫性をとるために,

#pragma omp flush

があります.ただし,barrierやcriticalなどの前後では明示的に指定しなくても 暗黙にflushされます.

共有メモリの資源を占有させるためのロックや,それを解放するためのアンロックもサポートされています. ロックを用いる場合は,omp_lock_t型のロック変数を用います.

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

計算例

円周率の計算

#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;
}

リンク


添付ファイル: fileopenmp_vc2005.jpg 1455件 [詳細]

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2024-03-08 (金) 18:06:05