自動スコープ宣言規則がどのように使用されるかを、次の例に示します。
例 6-3 より複雑な例
1. REAL FUNCTION FOO (N, X, Y) 2. INTEGER N, I 3. REAL X(*), Y(*) 4. REAL W, MM, M 5. 6. W = 0.0 7. 8. C$OMP PARALLEL DEFAULT(__AUTO) 9. 10. C$OMP SINGLE 11. M = 0.0 12. C$OMP END SINGLE 13. 14. MM = 0.0 15. 16. C$OMP DO 17. DO I = 1, N 18. T = X(I) 19. Y(I) = T 20. IF (MM .GT. T) THEN 21. W = W + T 22. MM = T 23. END IF 24. END DO 25. C$OMP END DO 26. 27. C$OMP CRITICAL 28. IF ( MM .GT. M ) THEN 29. M = MM 30. END IF 31. C$OMP END CRITICAL 32. 33. C$OMP END PARALLEL 34. 35. FOO = W - M 36. 37. RETURN 38. END
関数 FOO() には並列領域が 1 つあり、この並列領域には、SINGLE 構文とワークシェアリングの DO 構文、CRITICAL 構文がそれぞれ 1 つあります。こうした OpenMP 並列構文をすべて無視した場合、並列領域内のコードが行うのは、次のことです。
配列 X 内の値を配列 Y にコピーします。
X 内の正の最大値を検出し、その値を M に格納します。
X の一部要素の値を変数 W に蓄積します。
コンパイラが前述の規則に従って、この並列領域内の変数に適切なスコープを発見する仕組みをみてみましょう。
前述の並列領域では、I、N、MM、T、W、M、X、および Y という変数が使用されています。コンパイラは次のことを決定します。
スカラー I は、ワークシェアリング DO ループのループインデックスです。OpenMP 仕様では、I のスコープは PRIVATE 宣言することが必須です。
スカラー N は並列領域内で読み取られるだけで、データ競合を起こしません。このため、規則 S1 に従って、この変数のスコープは SHARED と宣言されます。
並列領域を実行するスレッドはすべて、スカラー MM の値を 0.0 に設定する 14 行目を実行します。この書き込みはデータ競合の原因になるため、規則 S1 は適用されません。この書き込みは、同じスレッド内のあらゆる MM の読み取りの前に起きるため、規則 S2 に従って、MM のスコープは PRIVATE と宣言されます。
同様に、T も PRIVATE とスコープ宣言されます。
スカラー W は 21 行目でいったん読み取られたあとに書き込まれます。このため、S1 および S2 は適用されません。加算は連想および伝達の両方の要素が含まれるため、規則 S3 に従って W のスコープは REDUCTION(+) と宣言されます。
スカラー M は、SINGLE 構文にある文 11 で書き込まれます。この SINGLE 構文の末尾のバリアは、文 11 の書き込みが文 28 の読み取りや文 29 の書き込みと同時に発生しないようにするためのものです。また、文 28 と 29 はどちらも CRITICAL 構文内にあるため、同時に発生しないようになっています。2 つのスレッドが同時に M にアクセスすることはできません。このため、並列領域内での M 読み取りと書き込みがデータ競合を起こすことはなく、規則 S1 に従って、M のスコープは SHARED と宣言されます。
配列 X は領域内では読み取りだけで、書き込みは行われません。このため、この配列のスコープは、規則 A1 に従って SHARED と宣言されます。
配列 Y への書き込みはスレッド間で分散され、2 つのスレッドが Y の同じ要素に書き込むことはありません。データの競合がないため、Y のスコープは、規則 A1 に従って SHARED と宣言されます。
例 6-4 QuickSort の例
static void par_quick_sort (int p, int r, float *data)
{
if (p < r)
{
int q = partition (p, r, data);
#pragma omp task default(__auto) if ((r-p)>=low_limit)
par_quick_sort (p, q-1, data);
#pragma omp task default(__auto) if ((r-p)>=low_limit)
par_quick_sort (q+1, r, data);
}
}
int main ()
{
...
#pragma omp parallel
{
#pragma omp single nowait
par_quick_sort (0, N-1, &Data[0]);
}
...
}
er_src result:
Source OpenMP region below has tag R1
Variables autoscoped as FIRSTPRIVATE in R1: p, q, data
Firstprivate variables in R1: data, p, q
47. #pragma omp task default(__auto) if ((r-p)>=low_limit)
48. par_quick_sort (p, q-1, data);
Source OpenMP region below has tag R2
Variables autoscoped as FIRSTPRIVATE in R2: q, r, data
Firstprivate variables in R2: data, q, r
49. #pragma omp task default(__auto) if ((r-p)>=low_limit)
50. par_quick_sort (q+1, r, data);
スカラー変数 p および q、およびポインタ変数データは、タスク構文でも並列領域でも読み取り専用です。 そのため、これらは TS1 に従って FIRSTPRIVATE と自動スコープ宣言されます。
例 6-5 もう 1 つの例
int fib (int n)
{
int x, y;
if (n < 2) return n;
#pragma omp task default(__auto)
x = fib(n - 1);
#pragma omp task default(__auto)
y = fib(n - 2);
#pragma omp taskwait
return x + y;
}
er_src result:
Source OpenMP region below has tag R1
Variables autoscoped as SHARED in R1: x
Variables autoscoped as FIRSTPRIVATE in R1: n
Shared variables in R1: x
Firstprivate variables in R1: n
24. #pragma omp task default(__auto) /* shared(x) firstprivate(n) */
25. x = fib(n - 1);
Source OpenMP region below has tag R2
Variables autoscoped as SHARED in R2: y
Variables autoscoped as FIRSTPRIVATE in R2: n
Shared variables in R2: y
Firstprivate variables in R2: n
26. #pragma omp task default(__auto) /* shared(y) firstprivate(n) */
27. y = fib(n - 2);
28.
29. #pragma omp taskwait
30. return x + y;
31. }
スカラー n はタスク構文でも並列構文でも読み取り専用です。そのため、n は TS1 に従って FIRSTPRIVATE と自動スコープ宣言されます。
スカラー変数 x および y は、ローカル関数 fib() のローカル変数です。両方のタスクが x と y にアクセスしても、データ競合は起こりません。taskwait があるため、2 つのタスクがまず実行を完了してから、タスクを検出後 fib() を実行していたスレッドが fib() を終了します。つまり、x と y は、2 つのタスクが実行している間でも利用可能なのです。そのため、x と y は、TS2 に従い、SHARED と自動スコープ宣言されます。
例 6-6 もう 1 つの例
int main(void)
{
int yy = 0;
#pragma omp parallel default(__auto) shared(yy)
{
int xx = 0;
#pragma omp single
{
#pragma omp task default(__auto) // task1
{
xx = 20;
}
}
#pragma omp task default(__auto) // task2
{
yy = xx;
}
}
return 0;
}
er_src result:
Source OpenMP region below has tag R1
Variables autoscoped as PRIVATE in R1: xx
Private variables in R1: xx
Shared variables in R1: yy
7. #pragma omp parallel default(__auto) shared(yy)
8. {
9. int xx = 0;
10.
Source OpenMP region below has tag R2
11. #pragma omp single
12. {
Source OpenMP region below has tag R3
Variables autoscoped as SHARED in R3: xx
Shared variables in R3: xx
13. #pragma omp task default(__auto) // task1
14. {
15. xx = 20;
16. }
17. }
18.
Source OpenMP region below has tag R4
Variables autoscoped as PRIVATE in R4: yy
Variables autoscoped as FIRSTPRIVATE in R4: xx
Private variables in R4: yy
Firstprivate variables in R4: xx
19. #pragma omp task default(__auto) // task2
20. {
21. yy = xx;
22. }
23. }
この例では、xx は並列領域の private 変数です。チームのスレッドの 1 つが xx の初期値を変更します (task1 を実行します)。その後、すべてのスレッドが task2 を検出し、xx が同じ計算を行います。
task1 では、xx を使用しても、データ競合は発生しません。1 つの構文の終わりに暗黙的なバリアーがあり、このバリアーを出る前に task1 を完了する必要があるので、xx は task1 が実行している間でも利用可能なのです。したがって、TS2 により、xx は task1 で SHARED と自動スコープ宣言されます。
task2 では、xx は読み取り専用として使用されます。ただし、xx の使用は、包含する並列構文では読み取り専用ではありません。xx は、並列構文に対しては PRIVATE と事前定義されているので、task2 が実行している間でも xx が利用可能かどうかはわかりません。そのため、TS3 に従い、task2 では xx は FIRSTPRIVATE と自動スコープ宣言されます。
task2 では、yy を使用することでデータ競合が発生し、task2 を実行する各スレッドでは、変数 yy は同じスレッドによる読み取りの前に常に書き込まれます。そのため、TS4 に従い、yy は task2 では PRIVATE と自動スコープ宣言されます。
例 6-7 もう 1 つの例
int foo(void)
{
int xx = 1, yy = 0;
#pragma omp parallel shared(xx,yy)
{
#pragma omp task default(__auto)
{
xx += 1;
#pragma omp atomic
yy += xx;
}
#pragma omp taskwait
}
return 0;
}
er_src result:
Source OpenMP region below has tag R1
Shared variables in R1: yy, xx
5. #pragma omp parallel shared(xx,yy)
6. {
Source OpenMP region below has tag R2
Variables autoscoped as SHARED in R2: yy
Variables autoscoped as FIRSTPRIVATE in R2: xx
Shared variables in R2: yy
Firstprivate variables in R2: xx
7. #pragma omp task default(__auto)
8. {
9. xx += 1;
10.
11. #pragma omp atomic
12. yy += xx;
13. }
14.
15. #pragma omp taskwait
16. }
task 構文では xx は読み取り専用として使用されないので、データ競合が発生します。しかし、タスク領域で x を読み取ると、タスク外で x の値が定義されます(この例では、xx は並列領域に対して SHARED なので、x の定義は並列領域外で行われます)。そのため、TS5 に従い、xx は FIRSTPRIVATE と自動スコープ宣言されます。
task 構文での yy の使用は読み取り専用ではありませんが、データ競合は発生しません。taskwait が発生しているので、yy はタスクの実行中でもアクセスできます。そのため、TS2 に従い、yy は SHARED と自動スコープ宣言されます。