OpenMP の並列領域で変数のスコープ属性を指定することを、スコープ宣言といいます。一般に、変数が SHARED とスコープ宣言された場合、すべてのスレッドは同じ変数を使用します。変数が PRIVATE スコープ宣言された場合は、各スレッドはそれぞれ専用の変数のコピーを使用します。OpenMP には、豊富なデータ環境があります。SHARED や PRIVATE に加えて、変数のスコープは、FIRSTPRIVATE、LASTPRIVATE、REDUCTION、あるいは THREADPRIVATE とも宣言できます。
OpenMP では、並列領域で使用する変数の 1 つ 1 つについて、そのスコープを宣言する必要があります。これは単調でエラーを起こしやすい工程で、多くの人が、OpenMP を使ってプログラムを並列化する作業でもっとも手間のかかる部分と認識しています。
Sun Studio C、C++、および Fortran 95 コンパイラには自動スコープ宣言機能があります。コンパイラが並列領域の実行および同期パターンを解析して、一群のスコープ宣言規則に基づいて、変数のスコープを自動的に決定します。
自動スコープ宣言用データスコープ節は、OpenMP 仕様に対する Sun の拡張です。このあとの 2 つある句のいずれかを利用することによって、変数のスコープを自動的に宣言するように指定できます。
構文は次のようになります。
並列構文上の __auto 節は、コンパイラが構文中で指定された変数のスコープを自動的に決定するように指示します。auto の前の下線は 2 つであることに注意してください。
__auto 節は、PARALLEL、PARALLEL DO/for、PARALLEL SECTIONS、または Fortran 95 PARALLEL WORKSHARE 指令で使用できます。
__auto 節で変数を指定した場合、ほかのデータスコープ節でその変数を指定できません。
並列構文上の default(__auto) 節は、どのデータスコープ節でも明示的にスコープ宣言されていない、構文内で参照される変数すべてのスコープを、コンパイラが自動的に特定するように指示します。
default(__auto) 節は、PARALLEL、PARALLEL DO/for、PARALLEL SECTIONS、または Fortran 95 PARALLEL WORKSHARE 指令で使用できます。
自動スコープ宣言では、コンパイラは、並列領域内の変数のスコープを決定する際に次の規則を適用します。
これらの規則は、OpenMP 仕様で暗黙にスコープ宣言される、ワークシェアリング DO または FOR ループのループインデックス変数などの変数には適用されません。
S1: 並列領域内で使用される変数が、その領域を実行するチーム内でのスレッドに関する「データ競合」状態になることがない場合、その変数のスコープは SHARED と宣言されます。
S2: 並列領域を実行するすべてのスレッドで、変数が同じスレッドによる読み取りの前につねに書き込まれる場合、その変数のスコープは PRIVATE と宣言されます。変数が PRIVATE とスコープ宣言することが可能で、並列領域のあと、書き込みの前に読み取られ、構文が PARALLEL DO か PARALLEL SECTIONS のいずれかである場合、その変数のスコープは、LASTPRIVATE と宣言されます。
S3: 変数がコンパイラの認識可能な縮約処理で使用されている場合、その変数のスコープは、その特定の型を持つ REDUCTION と宣言されます。
A1: 並列領域内で使用される配列が、その領域を実行するチーム内でのスレッドに関するデータ競合状態から自由の場合、その配列のスコープは SHARED と宣言されます。
暗黙のスコープを持たない変数を自動スコープ宣言するときに、コンパイラは、スカラーの場合は指定された順序で前述の S1 ~ S3 の規則に対して、および配列の場合は前述の A1 の規則に対して、変数の使われ方を比較検査します。規則に一致する場合、コンパイラはその規則に従って変数のスコープを決定します。規則に一致しない場合、コンパイラは次の規則を試します。コンパイラは、一致するものが見つからなかった場合は、その変数のスコープ判定を中止します。その変数のスコープは SHARED と宣言され、あたかも IF (.FALSE.) または if(0) 節が指定されているかのように結合並列領域が直列化されます。
自動スコープ宣言が失敗する理由は 2 つあります。1 つは、変数の使われ方が前述のどれにも一致しないため、もう 1 つは、ソースコードが複雑すぎて、コンパイラが十分な解析を行えないためです。こうした原因としてよくあるのは、たとえば、関数呼び出しや複雑な配列添え字、メモリー別名、ユーザー実装の同期などです。「6.5 現在の実装の既知の制限事項」を参照してください。
Fortran では、変数が __auto または default(__auto) 節を使用して自動スコープ宣言され、その変数に OpenMP 仕様に従って事前定義されたスコープがある場合、コンパイラはその事前定義されたスコープに従って変数のスコープを宣言します。
Fortran では、次の変数に事前定義されるスコープがあります。
threadprivate 指令の変数および共通ブロックは threadprivate です。
Do または並列 Do 構文の Do ループ内のループ反復変数は、その構文内では private です。
並列構文内の逐次ループでループ反復変数として使用される変数は、その並列構文内では private です。
暗黙の DO または FORALL インデックスは private です。
Cray ポインタの指示先は、Cray Fortran を関連付ける記憶領域の共有属性を継承します。
C/C++ では、変数が __auto または default(__auto) 節を使用して自動スコープ宣言され、その変数に OpenMP 仕様に従って事前定義されたスコープがある場合、コンパイラはその事前定義されたスコープに従って変数のスコープを宣言します。
C/C++ では、次の変数に事前定義されるスコープがあります。
threadprivate 指令の変数は threadprivate です。
構文内のスコープで宣言された自動記憶領域を持つ変数は private です。
割り当てられたヒープ記憶領域を持つ変数は shared です。
静的データメンバーは shared です。
for または parallel for 構文の for ループ内のループ反復変数は、その構文内では private です。
可変メンバーを持たない const-qualified 型の変数は shared です。
C および C++ の自動スコープ宣言が適用されるのは、基本データ型の 整数、浮動小数点、ポインタのみです。ユーザーが構造変数またはクラス変数の自動スコープ宣言を指定する場合、コンパイラは変数を shared としてスコープ宣言し、それを包含する並列領域は 1 つのスレッドとして実行されます。
「コンパイラのコメント」を利用して、詳細な自動スコープ宣言結果を調べたり、自動スコープ宣言が失敗したために直列化された並列領域がないか確認したりできます。
コンパイルで -g が付けられていると、コンパイラはインラインコメントを生成します。このコメントは、次に示すように er_src を使って表示できます(er_src コマンドは、Sun Studio ソフトウェアの一部として提供されています。詳細は、er_src(1) のマニュアルページまたは『Sun Studio プログラムのパフォーマンス解析』を参照してください)。
-xvpara コンパイルオプションを使用することからスタートすることを推奨します。自動スコープ宣言の失敗があると、次のような警告メッセージが出力されます。
%cat t.f INTEGER X(100), Y(100), I, T C$OMP PARALLEL DO DEFAULT(__AUTO) DO I=1, 100 T = Y(I) CALL FOO(X) X(I) = T*T END DO C$OMP END PARALLEL DO END %f95 -xopenmp -xO3 -vpara -c t.f "t.f", line 2: Warning: parallel region will be executed by a single thread because the autoscoping of following variables failed - x |
f95 では -vpara、cc では -xvpara 付きでコンパイルします (CC では、まだこのオプションが実装されていません)。
%cat t.f INTEGER X(100), Y(100), I, T C$OMP PARALLEL DO DEFAULT(__AUTO) DO I=1, 100 T = Y(I) X(I) = T*T END DO C$OMP END PARALLEL DO END %f95 -xopenmp -xO3 -g -c t.f %er_src t.o Source file: ./t.f Object file: ./ot.o Load Object: ./t.o 1. INTEGER X(100), Y(100), I, T Source OpenMP region below has tag R1 Variables autoscoped as SHARED in R1: x, y Variables autoscoped as PRIVATE in R1: t, i Private variables in R1: i, t Shared variables in R1: y, x 2. C$OMP PARALLEL DO DEFAULT(__AUTO) <Function: _$d1A2.MAIN_> Source loop below has tag L1 L1 parallelized by explicit user directive L1 parallel loop-body code placed in function _$d1A2.MAIN_ along with 0 inner loops Copy in M-function of loop below has tag L2 L2 scheduled with steady-state cycle count = 3 L2 unrolled 4 times L2 has 0 loads, 0 stores, 2 prefetches, 0 FPadds, 0 FPmuls, and 0 FPdivs per iteration L2 has 1 int-loads, 1 int-stores, 4 alu-ops, 1 muls, 0 int-divs and 1 shifts per iteration 3. DO I=1, 100 4. T = Y(I) 5. X(I) = T*T 6. END DO 7. C$OMP END PARALLEL DO 8. END |
次に、自動スコープ宣言の仕組みを示すより複雑な例を紹介します。
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 と宣言されます。
現在の Sun Studio Fortran 95 コンパイラの自動スコープ宣言に関する既知の制限事項は、次のとおりです。
解析では、OpenMP 指令のみ認識、使用されます。OpenMP 実行時ルーチンの呼び出しは認識されません。たとえばプログラムが OMP_SET_LOCK() および OMP_UNSET_LOCK() を使用してクリティカル領域を実装している場合、コンパイラはそのクリティカル領域の存在を検出できません。可能な場合は、CRITICAL および END CRITICAL 指令を使用してください。
解析では、BARRIER や MASTER などの OpenMP 同期指令を使用して指定された同期のみ認識、使用されます。ビジー待ちなどのユーザー実装の同期は認識されません。
コンパイルで -xopenmp=noopt が使用された場合、自動スコープ宣言はサポートされません。