A. 例
このドキュメントで定義されているコンストラクトの例を以下に示します。 ディレクティブの後のステートメントは、必要な場合にのみ複合です。非複合ステートメントは、その前にあるディレクティブからインデントされています。
A.1 並列の単純ループ
次の例は、parallel for ディレクティブを使用してループを並列化する方法を示しています。 ループ反復変数は既定でプライベートであるため、private 句で明示的に指定する必要はありません。
#pragma omp parallel for
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
A.2 条件付きコンパイル
次の例は、OpenMP マクロ _OPENMP を使用した条件付きコンパイルの使用方法を示しています。 OpenMP コンパイルにより、_OPENMP
マクロが定義されます。
# ifdef _OPENMP
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
定義されたプリプロセッサ演算子を使用すると、1 つのディレクティブで複数のマクロをテストできます。
# if defined(_OPENMP) && defined(VERBOSE)
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
A.3 並列領域
parallel ディレクティブは、粗粒度の並列プログラムで使用できます。 次の例では、並列領域の各スレッドは、スレッド番号に基づいて、グローバル配列 x
のどの部分を処理するかを決定します。
#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)
{
iam = omp_get_thread_num();
np = omp_get_num_threads();
ipoints = npoints / np;
subdomain(x, iam, ipoints);
}
A.4 nowait 句
並列領域内に多数の独立したループがある場合は、次のように、nowait 句を使用することで、for
ディレクティブの最後にある暗黙のバリアを回避できます。
#pragma omp parallel
{
#pragma omp for nowait
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
#pragma omp for nowait
for (i=0; i<m; i++)
y[i] = sqrt(z[i]);
}
A.5 critical ディレクティブ
次の例には、critical ディレクティブがいくつか含まれています。 この例は、タスクがデキューされて処理されるキュー モデルを示しています。 多数のスレッドが同じタスクをデキューするのを防ぐために、デキュー操作は critical
セクションに配置する必要があります。 この例の 2 つのキューは独立しているため、異なる名前 (xaxis と yaxis) の critical
ディレクティブによって保護されています。
#pragma omp parallel shared(x, y) private(x_next, y_next)
{
#pragma omp critical ( xaxis )
x_next = dequeue(x);
work(x_next);
#pragma omp critical ( yaxis )
y_next = dequeue(y);
work(y_next);
}
A.6 lastprivate 句
正しい実行が、ループの最後の反復で変数に割り当てる値に左右される場合があります。 このようなプログラムでは、変数の値が、ループを順次実行したときと同じになるように、このような変数をすべて lastprivate 句の引数として記載する必要があります。
#pragma omp parallel
{
#pragma omp for lastprivate(i)
for (i=0; i<n-1; i++)
a[i] = b[i] + b[i+1];
}
a[i]=b[i];
前の例では、並列領域の最後にある i
の値は、順次の場合と同様に、n-1
になります。
A.7 reduction 句
次の例は、reduction 句を示しています。
#pragma omp parallel for private(i) shared(x, y, n) \
reduction(+: a, b)
for (i=0; i<n; i++) {
a = a + x[i];
b = b + y[i];
}
A.8 parallel sections
次の例 (セクション 2.4.2 に対応) では、関数 xaxis、yaxis、zaxis を同時に実行できます。 最初の section
ディレクティブは省略可能です。 すべての section
ディレクティブは、parallel sections
コンストラクトの字句エクステントに出現する必要があります。
#pragma omp parallel sections
{
#pragma omp section
xaxis();
#pragma omp section
yaxis();
#pragma omp section
zaxis();
}
A.9 single ディレクティブ
次の例は、single ディレクティブを示しています。 この例では、1 つのスレッド (通常は single
ディレクティブに最初に遭遇したスレッド) だけが進行状況メッセージを出力します。 どのスレッドが single
セクションを実行するかについて、ユーザーが何かを想定することはできません。 他のすべてのスレッドは single
セクションをスキップし、single
コンストラクトの最後にあるバリアで停止します。 single
セクションを実行するスレッドを待たずに、他のスレッドが続行できる場合は、single
ディレクティブで nowait
句を指定できます。
#pragma omp parallel
{
#pragma omp single
printf_s("Beginning work1.\n");
work1();
#pragma omp single
printf_s("Finishing work1.\n");
#pragma omp single nowait
printf_s("Finished work1 and beginning work2.\n");
work2();
}
A.10 順次実行
ordered セクションは、並列で実行された処理からの出力を順番に並べる場合に役立ちます。 次のプログラムでは、インデックスが順番に出力されます。
#pragma omp for ordered schedule(dynamic)
for (i=lb; i<ub; i+=st)
work(i);
void work(int k)
{
#pragma omp ordered
printf_s(" %d", k);
}
A.11 固定数のスレッド
正しく実行するために、あらかじめ指定された固定数のスレッドに依存するプログラムもあります。 スレッド数の動的調整の既定の設定は実装で定義されるため、このようなプログラムでは、動的スレッド機能を無効にし、スレッド数を明示的に設定して、移植性を維持することもできます。 次の例は、omp_set_dynamic と omp_set_num_threads を使用してこれを行う方法を示しています。
omp_set_dynamic(0);
omp_set_num_threads(16);
#pragma omp parallel shared(x, npoints) private(iam, ipoints)
{
if (omp_get_num_threads() != 16)
abort();
iam = omp_get_thread_num();
ipoints = npoints/16;
do_by_16(x, iam, ipoints);
}
この例では、プログラムは 16 スレッドで実行された場合にのみ正しく実行されます。 実装が 16 スレッドをサポートできない場合、この例の動作は実装で定義されます。
並列領域を実行するスレッドの数は、動的スレッドの設定に関係なく、並列領域の間、一定に保たれます。 動的スレッド機構により、並列領域の開始時に使用するスレッド数が決定され、その領域の実行中は一定に保たれます。
A.12 atomic ディレクティブ
次の例では、atomic ディレクティブを使用して、競合状態 (複数のスレッドによる x の要素の同時更新) を回避しています。
#pragma omp parallel for shared(x, y, index, n)
for (i=0; i<n; i++)
{
#pragma omp atomic
x[index[i]] += work1(i);
y[i] += work2(i);
}
この例で atomic
ディレクティブを使用する利点は、x の 2 つの異なる要素の更新を並列で実行できることです。 代わりに critical ディレクティブを使用すると、x の要素に対するすべての更新が順次実行されます (ただし、順序は保証されません)。
atomic
ディレクティブは、その直後の C または C++ ステートメントにのみ適用されます。 そのため、この例では y の要素はアトミックに更新されません。
A.13 リストを指定した flush ディレクティブ
次の例では、スレッドのペア間で特定のオブジェクトのポイント ツー ポイント同期を行うために、flush
ディレクティブを使用しています。
int sync[NUMBER_OF_THREADS];
float work[NUMBER_OF_THREADS];
#pragma omp parallel private(iam,neighbor) shared(work,sync)
{
iam = omp_get_thread_num();
sync[iam] = 0;
#pragma omp barrier
// Do computation into my portion of work array
work[iam] = ...;
// Announce that I am done with my work
// The first flush ensures that my work is
// made visible before sync.
// The second flush ensures that sync is made visible.
#pragma omp flush(work)
sync[iam] = 1;
#pragma omp flush(sync)
// Wait for neighbor
neighbor = (iam>0 ? iam : omp_get_num_threads()) - 1;
while (sync[neighbor]==0)
{
#pragma omp flush(sync)
}
// Read neighbor's values of work array
... = work[neighbor];
}
A.14 リストのない flush ディレクティブ
次の例 (セクション 2.6.5 に対応) では、リストのない flush
ディレクティブの影響を受ける共有オブジェクトと影響を受けない共有オブジェクトを区別しています。
// omp_flush_without_list.c
#include <omp.h>
int x, *p = &x;
void f1(int *q)
{
*q = 1;
#pragma omp flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
void f2(int *q)
{
#pragma omp barrier
*q = 2;
#pragma omp barrier
// a barrier implies a flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
int g(int n)
{
int i = 1, j, sum = 0;
*p = 1;
#pragma omp parallel reduction(+: sum) num_threads(10)
{
f1(&j);
// i, n and sum were not flushed
// because they were not accessible in f1
// j was flushed because it was accessible
sum += j;
f2(&j);
// i, n, and sum were not flushed
// because they were not accessible in f2
// j was flushed because it was accessible
sum += i + j + *p + n;
}
return sum;
}
int main()
{
}
A.15 使用するスレッドの数
次の誤った例を考えてみましょう (セクション 3.1.2 に対応)。
np = omp_get_num_threads(); // misplaced
#pragma omp parallel for schedule(static)
for (i=0; i<np; i++)
work(i);
omp_get_num_threads()
呼び出しは、コードのシリアル セクションで 1 を返すので、前の例では np は常に 1 になります。 並列領域に配置されるスレッドの数を決定するには、この呼び出しを並列領域内に配置する必要があります。
次の例は、スレッド数のクエリを含めずにこのプログラムを書き換える方法を示しています。
#pragma omp parallel private(i)
{
i = omp_get_thread_num();
work(i);
}
A.16 ロック
次の例 (セクション 3.2 に対応) では、ロック関数の引数は omp_lock_t
型であり、それをフラッシュする必要はありません。 ロック関数により、スレッドは、最初のクリティカル セクションへのエントリを待機している間はアイドル状態になりますが、2 番目のセクションへのエントリを待機している間は他の処理を実行します。 omp_set_lock
関数はブロックしますが、omp_test_lock
関数はブロックしないため、skip()
の処理を実行できます。
// omp_using_locks.c
// compile with: /openmp /c
#include <stdio.h>
#include <omp.h>
void work(int);
void skip(int);
int main() {
omp_lock_t lck;
int id;
omp_init_lock(&lck);
#pragma omp parallel shared(lck) private(id)
{
id = omp_get_thread_num();
omp_set_lock(&lck);
printf_s("My thread id is %d.\n", id);
// only one thread at a time can execute this printf
omp_unset_lock(&lck);
while (! omp_test_lock(&lck)) {
skip(id); // we do not yet have the lock,
// so we must do something else
}
work(id); // we now have the lock
// and can do the work
omp_unset_lock(&lck);
}
omp_destroy_lock(&lck);
}
A.17 入れ子にできるロック
次の例 (セクション 3.2 に対応) は、入れ子にできるロックを使用して、構造体全体とそのメンバーの 1 つの両方に対する更新を同期する方法を示しています。
#include <omp.h>
typedef struct {int a,b; omp_nest_lock_t lck;} pair;
void incr_a(pair *p, int a)
{
// Called only from incr_pair, no need to lock.
p->a += a;
}
void incr_b(pair *p, int b)
{
// Called both from incr_pair and elsewhere,
// so need a nestable lock.
omp_set_nest_lock(&p->lck);
p->b += b;
omp_unset_nest_lock(&p->lck);
}
void incr_pair(pair *p, int a, int b)
{
omp_set_nest_lock(&p->lck);
incr_a(p, a);
incr_b(p, b);
omp_unset_nest_lock(&p->lck);
}
void f(pair *p)
{
extern int work1(), work2(), work3();
#pragma omp parallel sections
{
#pragma omp section
incr_pair(p, work1(), work2());
#pragma omp section
incr_b(p, work3());
}
}
A.18 入れ子になった for ディレクティブ
次の for
ディレクティブの入れ子の例は、内側と外側の for
ディレクティブが異なる並列領域にバインドされているため、準拠しています。
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
{
#pragma omp parallel shared(i, n)
{
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
前の例の次のバリエーションも準拠しています。
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
void work1(int i, int n)
{
int j;
#pragma omp parallel default(shared)
{
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
return;
}
A.19 work-sharing ディレクティブの間違った入れ子を示す例
このセクションの例は、ディレクティブの入れ子規則を示しています。
次の例は、内側と外側の for
ディレクティブが入れ子になっており、同じ parallel
ディレクティブにバインドされているため、準拠していません。
void wrong1(int n)
{
#pragma omp parallel default(shared)
{
int i, j;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
前の例の、次の動的に入れ子になるバージョンも準拠していません。
void wrong2(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
}
void work1(int i, int n)
{
int j;
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
次の例は、for
ディレクティブと single
ディレクティブが入れ子になっており、同じ並列領域にバインドされているため、準拠していません。
void wrong3(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp single
work(i);
}
}
}
次の例は、for
内の barrier
ディレクティブによってデッドロックが発生する可能性があるため、準拠していません。
void wrong4(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
work1(i);
#pragma omp barrier
work2(i);
}
}
}
次の例は、クリティカル セクションには一度に 1 つのスレッドしか入ることができないことから、barrier
によってデッドロックが発生するため、準拠していません。
void wrong5()
{
#pragma omp parallel
{
#pragma omp critical
{
work1();
#pragma omp barrier
work2();
}
}
}
次の例は、single
セクションを実行するスレッドは 1 つに限られることことから、barrier
によってデッドロックが発生するため、準拠していません。
void wrong6()
{
#pragma omp parallel
{
setup();
#pragma omp single
{
work1();
#pragma omp barrier
work2();
}
finish();
}
}
A.20 barrier ディレクティブのバインド
barrier
ディレクティブは、囲んでいる最も近い parallel
ディレクティブにバインドすることが、ディレクティブ バインディング規則で求められています。 ディレクティブ バインディングの詳細については、セクション 2.8 を参照してください。
次の例では、(sub3 内の) barrier
は sub2 の並列領域にバインドされているため、main からの sub2 の呼び出しは準拠しています。 barrier
はサブルーチン sub2 の並列領域にバインドされているため、main からの sub1 の呼び出しは準拠しています。 barrier
はどの並列領域にもバインドされておらず、無視されるため、main からの sub3 の呼び出しは準拠しています。 また、barrier
は、囲んでいる並列領域内のスレッドのチームのみを同期します。sub1 で作成されたすべてのスレッドを同期するわけではありません。
int main()
{
sub1(2);
sub2(2);
sub3(2);
}
void sub1(int n)
{
int i;
#pragma omp parallel private(i) shared(n)
{
#pragma omp for
for (i=0; i<n; i++)
sub2(i);
}
}
void sub2(int k)
{
#pragma omp parallel shared(k)
sub3(k);
}
void sub3(int n)
{
work(n);
#pragma omp barrier
work(n);
}
A.21 private 句を使用したスコープ変数
次の例の i
と j
の値は、並列領域の終了時に未定義になります。
int i, j;
i = 1;
j = 2;
#pragma omp parallel private(i) firstprivate(j)
{
i = 3;
j = j + 2;
}
printf_s("%d %d\n", i, j);
private
句の詳細については、セクション 2.7.2.1 を参照してください。
A.22 default(none) 句
次の例では、default(none)
句の影響を受ける変数と影響を受けない変数を区別しています。
// openmp_using_clausedefault.c
// compile with: /openmp
#include <stdio.h>
#include <omp.h>
int x, y, z[1000];
#pragma omp threadprivate(x)
void fun(int a) {
const int c = 1;
int i = 0;
#pragma omp parallel default(none) private(a) shared(z)
{
int j = omp_get_num_thread();
//O.K. - j is declared within parallel region
a = z[j]; // O.K. - a is listed in private clause
// - z is listed in shared clause
x = c; // O.K. - x is threadprivate
// - c has const-qualified type
z[i] = y; // C3052 error - cannot reference i or y here
#pragma omp for firstprivate(y)
for (i=0; i<10 ; i++) {
z[i] = y; // O.K. - i is the loop control variable
// - y is listed in firstprivate clause
}
z[i] = y; // Error - cannot reference i or y here
}
}
default
句の詳細については、セクション 2.7.2.5 を参照してください。
A.23 ordered ディレクティブの例
ordered
句で指定された for
には、複数の ordered セクションを含めることができます。 API では次の規則が指定されているため、最初の例は準拠していません。
"for
コンストラクトを使用したループの反復では、同じ ordered
ディレクティブを複数回実行することはできません。また、複数の ordered
ディレクティブを実行することはできません。" (セクション 2.6.6 を参照してください)。
この非準拠の例では、すべての反復で 2 つの ordered セクションが実行されます。
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
#pragma omp ordered
{ ... }
...
#pragma omp ordered
{ ... }
...
}
次の準拠の例は、複数の ordered セクションを含む for
を示しています。
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
if (i <= 10)
{
...
#pragma omp ordered
{ ... }
}
...
(i > 10)
{
...
#pragma omp ordered
{ ... }
}
...
}
A.24 private 句の例
並列領域の private 句は、領域の動的エクステントではなく、領域の字句エクステントでのみ有効です。 したがって、次の例では、ルーチン f の for
ループ内での変数 a の使用は、a のプライベート コピーを参照し、ルーチン g での使用は、グローバル a を参照します。
int a;
void f(int n)
{
a = 0;
#pragma omp parallel for private(a)
for (int i=1; i<n; i++)
{
a = i;
g(i, n);
d(a); // Private copy of "a"
...
}
...
void g(int k, int n)
{
h(k,a); // The global "a", not the private "a" in f
}
A.25 copyprivate データ属性句の例
例 1: copyprivate 句を使用して、1 つのスレッドによって取得された値を、他のスレッドのプライベート変数のすべてのインスタンスに直接ブロードキャストできます。
float x, y;
#pragma omp threadprivate(x, y)
void init( )
{
float a;
float b;
#pragma omp single copyprivate(a,b,x,y)
{
get_values(a,b,x,y);
}
use_values(a, b, x, y);
}
ルーチン init がシリアル領域から呼び出された場合、その動作はディレクティブの存在の影響を受けません。 get_values ルーチンの呼び出しが 1 つのスレッドによって実行された後、すべてのスレッドの a、b、x、y で指定されたプライベート オブジェクトが読み取られた値で定義されるまで、どのスレッドもコンストラクトから離れることはありません。
例 2: 前の例とは異なり、特定のスレッド (マスター スレッドなど) によって読み取りが実行される必要があるとします。 この場合、copyprivate
句を使用して、ブロードキャストを直接実行することはできませんが、一時的な共有オブジェクトへのアクセスを提供することは可能です。
float read_next( )
{
float * tmp;
float return_val;
#pragma omp single copyprivate(tmp)
{
tmp = (float *) malloc(sizeof(float));
}
#pragma omp master
{
get_float( tmp );
}
#pragma omp barrier
return_val = *tmp;
#pragma omp barrier
#pragma omp single
{
free(tmp);
}
return return_val;
}
例 3: 並列領域に入る前に、その領域内で必要なロック オブジェクトの数を簡単に決定できないとします。 copyprivate
句を使用して、その並列領域内で割り当てられている共有ロック オブジェクトへのアクセスを提供できます。
#include <omp.h>
omp_lock_t *new_lock()
{
omp_lock_t *lock_ptr;
#pragma omp single copyprivate(lock_ptr)
{
lock_ptr = (omp_lock_t *) malloc(sizeof(omp_lock_t));
omp_init_lock( lock_ptr );
}
return lock_ptr;
}
A.26 threadprivate ディレクティブ
次の例は、threadprivate ディレクティブを使用して、各スレッドに個別のカウンターを提供する方法を示しています。
例 1
int counter = 0;
#pragma omp threadprivate(counter)
int sub()
{
counter++;
return(counter);
}
例 2
int sub()
{
static int counter = 0;
#pragma omp threadprivate(counter)
counter++;
return(counter);
}
A.27 C99 可変長配列
次の例は、firstprivate ディレクティブで C99 可変長配列 (VLA) を使用する方法を示しています。
Note
可変長配列は、Visual C++ では現在サポートされていません。
void f(int m, int C[m][m])
{
double v1[m];
...
#pragma omp parallel firstprivate(C, v1)
...
}
A.28 num_threads 句
次の例は、num_threads 句を示しています。 並列領域は、最大 10 スレッドで実行されます。
#include <omp.h>
main()
{
omp_set_dynamic(1);
...
#pragma omp parallel num_threads(10)
{
... parallel region ...
}
}
A.29 critical コンストラクト内の work-sharing コンストラクト
次の例は、critical
コンストラクト内で work-sharing コンストラクトを使用する方法を示しています。 この例は、work-sharing コンストラクトと critical
コンストラクトが同じ並列領域にバインドされていないため、準拠しています。
void f()
{
int i = 1;
#pragma omp parallel sections
{
#pragma omp section
{
#pragma omp critical (name)
{
#pragma omp parallel
{
#pragma omp single
{
i++;
}
}
}
}
}
}
A.30 再プライベート化
次の例は、変数の再プライベート化を示しています。 プライベート変数は、入れ子になったディレクティブで再度 private
としてマークできます。 囲んでいる並列領域内でこれらの変数を共有する必要はありません。
int i, a;
...
#pragma omp parallel private(a)
{
...
#pragma omp parallel for private(a)
for (i=0; i<10; i++)
{
...
}
}
A.31 スレッドセーフなロック関数
次の C++ の例は、omp_init_lock を使用して並列領域のロックの配列を初期化する方法を示しています。
// A_13_omp_init_lock.cpp
// compile with: /openmp
#include <omp.h>
omp_lock_t *new_locks() {
int i;
omp_lock_t *lock = new omp_lock_t[1000];
#pragma omp parallel for private(i)
for (i = 0 ; i < 1000 ; i++)
omp_init_lock(&lock[i]);
return lock;
}
int main () {}