5. OpenMP

5.1 OpenMPとは

OpenMP[3][4]とは、共有メモリー環境で並列計算を行うプログラミング技術であり、 複数のコアを持ったCPUで、 同時に複数のスレッドを実行して処理を分割し計算時間を短縮するものです。 1台のコンピュータに複数のCPUが搭載されていればさらにその個数だけ速くなります。
OpenMPは次節で述べるマルチスレッドの一種ですが、 プログラミング方法が異なりますので、ここでは特に"OpenMP"と呼びます。

5.2 OpenMPプログラミング

OpenMPでは並列処理を行うfor文のすぐ上に"#pragma omp"で始まる指示文(ディレクティブ)を挿入し、 コンパイルオプションでOpenMPを使用していることを知らせます。
コンパイルオプションがないときは指示文はコメントとみなされ 1コアのみを使用する逐次プログラムができます。
OpenMPは元のソースコードに一切手を加えることなく並列化できますので、 OpenMPで並列化できるアルゴリズムのときは最も簡単にプログラミングできます。 また、並列化したい場所だけを順々に並列化できますのでプログラミングの生産性が高くなります。
なお、OpenMPはOSに依存しない規格ですので、同じソースコードがWindowsとLinuxで動きます。

5.3 OpenMPプログラミング例

リスト5-1にベクトルの内積をOpenMPで並列計算するプログラムを示します。


リスト5-1 OpenMPプログラム(sdot_omp.c)
     1	/*
     2	dot product (OpenMP)
     3	
     4	VC++ : cl /Ox /openmp sdot_omp.c
     5	gcc  : gcc -O3 -std=c99 -fopenmp sdot_omp.c -o sdot_omp
     6	
     7	Usage :
     8	> sdot_omp <num> <loop> <thread>
     9	*/
    10	
    11	#include <stdlib.h>
    12	#include <stdio.h>
    13	#include <time.h>
    14	#include <omp.h>
    15	
    16	#ifndef _WIN32
    17	#include <sys/time.h>
    18	#endif
    19	
    20	static double sdot(int n, float *a, float *b)
    21	{
    22		double sum = 0;
    23		int    i;
    24	#pragma omp parallel for reduction(+:sum)
    25		for (i = 0; i < n; i++) {
    26			sum += a[i] * b[i];
    27		}
    28	
    29		return sum;
    30	}
    31	
    32	int main(int argc, char **argv)
    33	{
    34		int    nthread = 1;
    35		int    n = 1000;
    36		int    nloop = 1000;
    37	#ifdef _WIN32
    38		clock_t t0, t1;
    39	#else
    40		struct timeval t0, t1;
    41	#endif
    42	
    43		// arguments
    44		if (argc >= 4) {
    45			n = atoi(argv[1]);
    46			nloop = atoi(argv[2]);
    47			nthread = atoi(argv[3]);
    48		}
    49	
    50		// thread
    51		omp_set_num_threads(nthread);
    52	
    53		// alloc
    54		size_t size = n * sizeof(float);
    55		float *a = (float *)malloc(size);
    56		float *b = (float *)malloc(size);
    57	
    58		// setup problem
    59		for (int i = 0; i < n; i++) {
    60			a[i] = i + 1.0f;
    61			b[i] = i + 1.0f;
    62		}
    63	
    64		// timer
    65	#ifdef _WIN32
    66		t0 = clock();
    67	#else
    68		gettimeofday(&t0, NULL);
    69	#endif
    70	
    71		// calculation
    72		double sum = 0;
    73		for (int loop = 0; loop < nloop; loop++) {
    74			sum += sdot(n, a, b);
    75		}
    76	
    77		// timer
    78		double cpu = 0;
    79	#ifdef _WIN32
    80		t1 = clock();
    81		cpu = (double)(t1 - t0) / CLOCKS_PER_SEC;
    82	#else
    83		gettimeofday(&t1, NULL);
    84		cpu = (t1.tv_sec - t0.tv_sec) + 1e-6 * (t1.tv_usec - t0.tv_usec);
    85	#endif
    86	
    87		// output
    88		double exact = (double)nloop * n * (n + 1) * (2 * n + 1) / 6.0;
    89		printf("n=%d nloop=%d nthread=%d dot=%.6e(%.6e) cpu[sec]=%.3f\n",
    90			n, nloop, nthread, sum, exact, cpu);
    91	
    92		// free
    93		free(a);
    94		free(b);
    95	
    96		return 0;
    97	}

OpenMP指示文
24行目でfor文を並列計算することを指示しています。 さらにベクトルの内積を並列計算するには、 すべてのスレッドが総和にアクセスしますので以下の"reduction"指示文が必要になります。
reduction (operatorlist)
ここで operator は加算("+")、乗算("*")などを表し、 list はreduction演算される変数名を表します。
なお、変数の依存性がなくreduction操作が必要ないときは単に次の指示文で十分です。
#pragma omp parallel for

OpenMP関数
51行目のomp_set_num_threads関数は使用するスレッド数を指定する関数です。 本関数を呼ばないときは論理コア数が指定されます。
OpenMP関数を使用するには14行目のinclude文が必要になります。
OpenMPにはこの他にいくつかの関数があります。 使用頻度は高くありませんが、詳しくは[3][4]を参考にして下さい。

計算時間の計測
gccではclock関数は全スレッドの和になりますので、 リスト5-1では計算時間を計測するためにgettimeofday関数を使用しています。

コンパイル・リンク方法
VC++ではコンパイルオプションに"/openmp"が必要です。
> cl /Ox /openmp sdot_omp.c
gccではコンパイルオプションとリンクオプションに"-fopenmp"が必要です。
> gcc -O3 -std=c99 -fopenmp sdot_omp.c -o sdot_omp

プログラムの実行方法
プログラムの実行方法は以下の通りです。
> sdot_omp 配列の大きさ 繰り返し回数 スレッド数
例えば以下のようになります。
> sdot_omp 1000000 1000 4
繰り返し回数は計算時間の測定誤差を小さくするためです。
なお、OpenMP対応プログラムの実行にはvcomp140.dllファイルが必要です。 (Microsoft Visual Studio 2015の場合)

5.4 OpenMPの計算時間

表5-1に計算時間を示します。
配列の大きさ(=N)と繰り返し回数(=L)の積は一定(=10^10)です。従って全体の演算量は同じです。
表の下になるほど使用メモリーが小さくなりキャッシュに乗りやすくなります。
1スレッドではNo.1-4で計算時間はほぼ一定です。
No.2-3では4スレッドで3-4倍速くなっており、十分な性能が得られることがわかります。
No.4ではスレッドの起動回数(=L)が多いために計算時間が少し増えています。

表5-1 OpenMPの計算時間(()内は1スレッドとの速度比)
No.配列の大きさN繰り返し回数LWindowsLinux
1スレッド4スレッド1スレッド4スレッド
110,000,0001,0008.76秒(1.0)5.80秒(1.51)10.77秒(1.0)7.11秒(1.52)
21,000,00010,0008.07秒(1.0)2.28秒(3.54)10.23秒(1.0)3.13秒(3.27)
3100,000100,0007.58秒(1.0)2.12秒(3.58)9.18秒(1.0)2.57秒(3.58)
410,0001,000,0007.74秒(1.0)2.31秒(3.35)9.13秒(1.0)3.32秒(2.75)