OpenNH

日常のひとこま(自分用のメモとかあれこれ)

VisualC++でOpenMPを使ってみる


OpenMP

OpenMPとはマルチコアCPUによるメモリ共有型の並列化を簡単に実装できる並列化技術です。

開発環境

Visual Studio 2015 c++
Windows 10 Home
Intel Core i7-6700K

初めてのOpenMP

まず最初に新規プロジェクトで空のアプリケーションを作って、以下のソースを実行してみましょう。
この例では、OpenMPの実行時ライブラリを使用しないので、インクルードする必要はありませんが、後々使うので一応インクルードしています。

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

int main()
{
#pragma omp parallel
	printf("Hello OpenMP!\n");

	return 0;
}

でも、このままじゃ並列化されていません。
以下の2点の作業を行って初めて並列化されます。

  • [プロジェクトのプロパティ]>[構成プロパティ]>[C++]>[言語]>「OpenMPのサポート」を「はい」にする。
  • プロジェクトを構成をReleaseに変更してからビルドする。

これで実行すると…
コア数分だけ「printf("Hello OpenMP!\n");」の部分が表示されるはずです。私のCPUではコア数が8個ですので、8個に並列化されています。
f:id:FounderLeis:20181009200706p:plain

OpenMPでは「#pragma」を入れるだけで並列化できるので非常に簡単ですね!

並列化数を指定

任意の数だけ並列化させたい場合は、「#pragma omp parallel」を「#pragma omp parallel num_threads('任意の数')」に変更するだけです。

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

int main()
{
#pragma omp parallel num_threads(5)
	printf("Hello OpenMP!\n");

	return 0;
}

実行結果を示します。
f:id:FounderLeis:20181009202045p:plain
指定した数(=5)だけ並列されていますね。

forループを並列化

OpenMPに任せると、適当にループを並列化してくれます。
forループ並列化の例を載せます。

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

int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int b[10] = { 0 };
	int i;

#pragma omp parallel num_threads(2)
#pragma omp for
	for (i = 0; i < 10; i++) {
		b[i] = a[i] * a[i];

		printf(" i=%d,  thread_ID=%d,  thread_Num=%d\n",
			i, omp_get_thread_num(), omp_get_num_threads());
	}

	for (i = 0; i < 10; i++)
		printf(" b[%d] = %d\n", i, b[i]);

	return 0;
}

この例では、forループが”i=0~4”と”i=5~10”の2つのループに分割されて計算されます。
実行すると以下のような結果が表示されます。
f:id:FounderLeis:20181009203914p:plain
スレッド0とスレッド1の動作順序は保証されるわけではありませんので、動作環境によっては順序が入れ替わることもあります。また、配列の代入順序とコマンドプロンプトへの表示順序が同じである保証もありません。

セクションで並列化

OpenMPではループの並列化によく使われますが、プログラムをブロック(セクション)分けして並列化することもにも使えます。
それがsectionsのコマンドです。以下に、各ブロックを並列化した例を示します。

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

int main()
{
#pragma omp parallel
#pragma omp sections
	{
#pragma omp section
		printf("section0: thread_ID=%d, thread_Num=%d\n",
			omp_get_thread_num(), omp_get_num_threads());

#pragma omp section
		printf("section1: thread_ID=%d, thread_Num=%d\n",
			omp_get_thread_num(), omp_get_num_threads());

#pragma omp section
		printf("section2: thread_ID=%d, thread_Num=%d\n",
			omp_get_thread_num(), omp_get_num_threads());
	}

	return 0;
}

実行すると以下のような結果が表示されます。
f:id:FounderLeis:20181009223705p:plain
このようにして、単純なループだけでなく異なる処理を並列化することが簡単にできます。上記の実行例では、8スレッドの内0,1,3スレッドで並列化されていることが分かります。

OpenMPとマルチスレッドの記述比較

OpenMPとスレッドを比較するため、"Hello OpenMP!"と10回表示させるだけの簡単なプログラムを両方の方法で記述していきます。マルチスレッドプログラムの書き方はいろいろありますが、今回はWindows APIを使用してみます。


OpenMPでのソース

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

int main()
{
#pragma omp parallel num_threads(10)
	printf("Hello OpenMP!\n");

	return 0;
}


↓マルチスレッドでのソース

#include <Windows.h>
#include <stdio.h>

void threadProc(void) 
{
	printf("Hello OpenMP!\n");
}

int main()
{
	HANDLE hThread[10];

	for (int i = 0; i < 10; i++) {
		hThread[i] = CreateThread(0, 0, 
			(LPTHREAD_START_ROUTINE)threadProc, NULL, 0, NULL);
	}

	WaitForMultipleObjects(10, hThread, TRUE, INFINITE);

	return 0;
}

使い方によりますが、圧倒的にOpenMPの方がすっきりしていて使いやすいですね!マルチスレッドプログラミングは結構めんどくさいのです…

まとめ

OpenMPの簡単な使い方をまとめてみました。
OpenMPとは…端的に述べると、

  • 簡単に並列化できるスレッド技術!
  • スレッドプログラムで面倒なところが最適化される!

って感じでしょうか。

OpenMPを使い始める方の参考にになればと思います。