変数の局所化
並列処理における変数の鉄則は、「スコープは極力狭く」です。並列処理では、変数にアクセスできるコードの数に比例して、矛盾がない状態を保つのが難しくなります。従って、変数にアクセスできるコードを少なくすることが大切です。
OpenMPには、スレッド内でのみ変数を共有する仕組みが定義されています。この機能を使えば、変数のスコープを狭くし、変数にアクセスするコードを減らすことができます。
これからその機能の使用法を、サンプルプロジェクトPrivateSampleのコードで示します。
#include <stdio.h>
#include <omp.h>
int main()
{
int x, y;
/*
ここで変数を初期化して実行するとエラーが発生する
x = 0;
y = 0;
*/
#pragma omp parallel private( x, y )
{
/* このコメントを外すとエラーが発生
if ( x == 10 )
printf( "x = %d", x );
if ( y == 20 )
printf( "y = %d", y );
*/
/* プライベート変数に値を代入して表示 */
x = omp_get_thread_num();
y = omp_get_thread_num() + 1;
printf( "スレッド:%d\t Xの値 = %d, Yの値 = %d\n", omp_get_thread_num(), x, y );
/* 次のコードは他のスレッドに影響を与えない */
x = 10;
y = 20;
}
/* 変数を代入しないとエラーが発生する */
x = 0;
y = 0;
/* 最終値を表示 */
printf( "【最終値】\t Xの値 = %d, Yの値 = %d\n\n", x, y );
return 0;
}
OpenMPのprivate指示句を使えば、指定した変数をスレッド内でのみ使用できるようになります。本記事では、以後この変数をスレッドプライベート変数と呼びます。
複数の変数を指定したい時は、 private( x, y )のように、括弧内で変数を列挙します。
スレッドプライベート変数は、他のスレッドが持つ同名の変数と、ブロックの外にある変数に影響を与えません。サンプルコードのx = 10;y = 20;が、最終値として反映されず、改めて変数に0を代入しているのはこのためです。
private指示句を使用するに当たって、一つ注意するべき点があります。それは、「スレッドプライベートに指定した変数の値は不定」である点です。#pragma omp paralleのブロック内で変数を使う前に、スレッドプライベート変数は必ず初期化せねばなりません。初期化しないとプログラムが異常終了します。
試しに先ほどのサンプルのコードを、#pragma omp paralleの直前で変数を初期化し、ブロック内で変数を初期化しないように変更してから実行してください。エラーを報告するダイアログが表示されます。
次に先ほど行った変更を取り消してください。そして、このコメントを外すとエラーが発生と書かれているコードのコメントを解除し、再コンパイル後に実行してください。変数がまだ初期化されていないのでエラーが発生します。private指示句を使用する際には、必ずブロック内で変数を初期化してください。
このようにprivate指示句は、スレッドで使用する変数の値をあらかじめ決められず、変数の初期化を忘れるとエラーが生じます。この動作が不適切な場合は、firstprivate指示句、またはlastprivate指示句を使用するとよいでしょう。
これからfirstprivate指示句の使い方を、サンプルプロジェクトFirstPrivateSampleのコードを用いて解説します。
#include <stdio.h>
#include <omp.h>
int main()
{
int x, y;
/* 変数を初期化する */
x = 0;
y = 0;
#pragma omp parallel firstprivate( x, y )
{
/* 以下のプログラムは絶対に実行されない */
if ( x == 10 ) printf( "x = %d\n", x );
if ( y == 20 ) printf( "x = %d\n", y );
/* 変数の初期値を表示 */
printf( "スレッド:%d\t Xの値 = %d, Yの値 = %d\n", omp_get_thread_num(), x, y );
/* 以下のプログラムは、他のスレッドとブロック外に影響を与えない。 */
x = 10;
y = 20;
}
/* 最終値を表示 */
printf( "【最終値】\t Xの値 = %d, Yの値 = %d\n\n", x, y );
return 0;
}
このコードを読めば、各スレッドに共通した初期値を設定できることが分かります。firstprivate指示句を使うと、各スレッドが使用する、スレッドプライベート変数の初期値を指定することができます。
次は、lastprivate指示句の使い方を、サンプルプロジェクトLastPrivateSampleのコードを用いて解説します。
#include <stdio.h>
#include <omp.h>
int main()
{
int x, y, i;
x = 0;
y = 0;
#pragma omp parallel for firstprivate( x, y ) lastprivate( x, y )
for ( i = 0; i < 10; i++ )
{
x += i;
y += i;
}
/* 最終値を表示 */
printf( "【最終値】\t Xの値 = %d, Yの値 = %d\n\n", x, y );
return 0;
}
このコードは、各スレッドで0~9の値の合計値を算出するもので、lastprivate指示句の特徴が表れています。
第一に、firstprivate指示句とは違いparallel構文(#pragma omp parallel)で指定できません。第二に、ブロック外に影響を与えます。第三に、初期化もしくは変数に値を設定する部分で、意図せず正しくない結果を出してしまう危険性があります。使用する際には注意が必要です。
例えば、LastPrivateSampleのコードは、正しい方法で変数を初期化していないので、合計値が誤ったものになります。lastprivate指示句は、並列処理の結果を取得したい時に使用するとよいでしょう。
変数をスレッドのプライベート変数にする方法を解説しました。しかし、その逆に変数を共有したい場合もあります。その場合はshared指示句を使用するか、他の指示句(firstprivate指示句など)を指定しないようにします。
スレッド間で変数を共有する方法を、サンプルプロジェクトSharedSampleのコードを用いて解説します。
#include <stdio.h>
#include <omp.h>
int main()
{
int x, y, i;
x = 0;
y = 0;
#pragma omp parallel
{
/* 変数を共有 */
#pragma for shared( x, y )
for ( i = 0; i < 10; i++ )
{
x += i;
y += i;
}
printf( "スレッド:%d\t Xの値 = %d, Yの値 = %d\n", omp_get_thread_num(), x, y );
}
printf( "【処理結果】\t Xの値 = %d, Yの値 = %d\n\n", x, y );
/* このプログラムでも処理結果は同じ */
x = 0;
y = 0;
#pragma omp parallel
{
#pragma for
for ( i = 0; i < 10; i++ )
{
x += i;
y += i;
}
printf( "スレッド:%d\t Xの値 = %d, Yの値 = %d\n", omp_get_thread_num(), x, y );
}
printf( "【処理結果】\t Xの値 = %d, Yの値 = %d\n\n", x, y );
return 0;
}
このコードは、先ほどのサンプルプロジェクトLastPrivateSampleと内容が同じで、shared指示句を明示した場合と、明示していない場合を示しています。
変数が共有されているので、ブロック内で変数を初期化しないと変数の値が累積されます。サンプルで示したように、shared指示句は明示的に指定しなくてもよいのですが、プログラマが意図していないのにも関わらず、変数が共有化されてしまうこともありますので、指示句を明示的に指定する方がいいでしょう。
以上でこの項の解説は終わりです。次項では、各スレッドが持つプライベート変数に値をコピーする方法について解説します。