インテル® コンパイラーは、OpenMP 宣言子を含む Fortran プログラムを入力ファイルとして処理し、マルチスレッド・バージョンのコードを生成します。並列プログラムが実行を開始する際に存在する単一スレッドは、初期スレッドと呼ばれます。
初期スレッドは、並列領域に到達すると、チームのマスタースレッドになることができます。並列領域に到達するまで、初期スレッドはシーケンシャルに処理を続行します。
並列領域とは、1 つのスレッドチームによって並列に実行されなければならない 1 ブロックのコードです。OpenMP API では、並列構造は OpenMP 宣言子 PARALLEL をコードセグメントの最初に、宣言子 END PARALLEL を最後に配置して定義されます。このように境界のあるコードセグメントは、並列実行が可能です。
コードの構造ブロックとは、最初と最後に単一の入口/出口ポイントを持つ、1 つまたは複数の実行文の集まりです。
インテル® Visual Fortran コンパイラーはワークシェアリング構造および同期化構造をサポートします。これらの各構造は、特定の 1 つまたは 2 つの OpenMP 宣言子と、囲まれた、または後続するコードの構造ブロックから構成されます。
並列領域の最後で、スレッドはすべてのチームメンバーが到達するまで待機します。そして、チームは論理的になくなり (次の並列領域で再利用されることもあります)、マスタースレッドは次の並列領域が検出されるまでシーケンシャルに処理を続行します。
ワークシェアリング構造は、それを囲む並列領域に入る時点で作成されたチームメンバー間で、囲まれたコード領域の実行を分割します。マスタースレッドが並列領域に入ると、スレッドチームが形成されます。並列領域の最初から開始して、ワークシェアリング構造が検出されるまで、すべてのチームメンバーによってコードは実行されます。ワークシェアリング構造は、チームのメンバー間で、囲まれたコード領域の実行を分割します。
OpenMP の SECTIONS または DO 構造は、その囲まれた作業を現在のチームのスレッド間に分配するため、ワークシェアリング構造として定義されます。ワークシェアリング構造は、並列領域の動的実行中である場合にのみ分配されます。ワークシェアリング構造が並列領域の記述範囲内にあれば、チームのメンバー間で処理を分配することにより、そのワークシェアリング構造は常に実行されます。ワークシェアリング構造が並列領域に字句的 (明示的) に囲まれていない場合 (つまり、構造が孤立 (orphan) している場合)、そのワークシェアリング構造は、最も近い動的範囲の並列領域のチームメンバー間に分配されます (これが存在する場合)。動的範囲の並列領域が存在しない場合、構造はシーケンシャルに実行されます。
1 つのスレッドがワークシェアリング構造の最後に到達すると、構造内のすべてのチームメンバーが処理を終了するまで待機する場合があります。ワークシェアリング構造によって定義されたすべての処理が終了すると、チームはワークシェアリング構造を出て、後に続くコードの実行を続行します。
並列/ワークシェアリング複合構造とは、ワークシェアリング構造を 1 つだけ含む並列領域を示します。
並列処理宣言子には次のグループが含まれます。
PARALLEL および END PARALLEL
DO および END DO 宣言子は、ループの繰り返しを並列実行するように指定します。
SECTIONS および END SECTIONS 宣言子は、任意のシーケンシャル・コードを並列実行するように指定します。各 SECTION はチーム内の 1 つのスレッドにより 1 回実行されます。
SINGLE および END SINGLE 宣言子は、1 つのスレッドによってのみ実行されるコードのセクションを定義します。このセクションを実行するように指定されていないスレッドはコードを無視します。
並列/ワークシェアリング複合構造は、単一のワークシェアリング構造が含まれる並列領域を指定するための短縮形を提供します。並列/ワークシェアリング複合構造は次のとおりです。
PARALLEL DO および END PARALLEL DO
PARALLEL SECTIONS および END PARALLEL SECTIONS
WORKSHARE および PARALLEL WORKSHARE
同期化とは、共有データの一貫性を保証し、スレッド間の並列実行を調整するスレッド間通信です。データがアクセスされる際にすべてのスレッドが同じ値を取得する場合、スレッドのチームの共有データは一貫しています。同期化構造は、共有データの一貫性を保証するために使用されます。
OpenMP の同期化宣言子には、CRITICAL、ORDERED、ATOMIC、FLUSH および BARRIER があります。
宣言子 |
使用方法 |
---|---|
CRITICAL |
並列領域またはワークシェアリング構造内では、一度に 1 つのスレッドのみが CRITICAL 構造のコードを実行することができます。 |
ORDERED |
DO または SECTIONS 構造とともに使用され、コード部分を順次実行します。 |
ATOMIC |
他のスレッドに割り込まれずにメモリー位置を更新するために使用されます。 |
FLUSH |
チーム内のすべてのスレッドが一貫性のあるメモリーのビューを持つために使用されます。 |
BARRIER |
コード内の特定の位置ですべてのチームのメンバーが集合するために使用します。BARRIER を実行する各チームのメンバーは、他のメンバーが到達するまで BARRIER で待機します。ワークシェアリングまたは他の同期化構造内では、デッドロックの可能性があるため BARRIER を使用することはできません。 |
MASTER |
マスタースレッドで実行するために使用します。 |
詳細は、「OpenMP* 宣言子と節の概要」のリストを参照してください。
データの共有は、SHARED および PRIVATE 節を使用して、並列領域またはワークシェアリング構造の最初で指定されます。SHARED 節に含まれているすべての変数は、チームメンバー間で共有されます。アプリケーションでは次の手順が必要となります。
これらの変数へのアクセスを同期化します。
PRIVATE 節に含まれているすべての変数は、各チームのメンバーに対してプライベートであることを保証します。並列領域全体では、例えば、t チームメンバーでは、PRIVATE 節に含まれているすべての変数の t+1 のコピーができます (アクティブなグローバルコピーが並列領域外に 1 つ、各チームのメンバー用に PRIVATE のコピーが 1 つあります)。
FIRSTPRIVATE 節が指定されていない限り、並列領域の開始時点で PRIVATE 変数を初期化します。この場合、FIRSTPRIVATE 節が指定されている構造の開始時点で PRIVATE コピーはグローバルコピーによって初期化されます。
並列領域の最後で PRIVATE 変数のグローバルコピーを更新します。しかし、DO 宣言子の LASTPRIVATE 節を使用することで、ループの最後の繰り返しを順次実行したチームメンバーからのグローバルコピーを更新することができます。
また、SHARED および PRIVATE 変数に加えて、THREADPRIVATE 宣言子を使用することで個々の変数と共通ブロック全体をプライベート化することができます。
OpenMP には、並列宣言子の表現力を大幅に向上する孤立化と呼ばれる機能が含まれています。孤立化では、並列領域に関連付けられている宣言子が 1 つのプログラムユニットの記述範囲内にある必要がありません。CRITICAL、BARRIER、SECTIONS、SINGLE、MASTER、DO および TASK などの宣言子は、ランタイム時に、囲まれた並列領域に動的に "バインド" し、プログラムユニット内に出現することができます。
孤立宣言子は、コードに最小限の変更を行うだけで、既存のコードを並列処理することができます。また、孤立化は、1 つの並列領域を、呼び出されたサブルーチン内にある複数の DO 宣言子とバインドすることでパフォーマンスの向上を可能にします。次のコードセグメントを参照してください。
例 |
---|
subroutine phase1 integer :: i !$OMP DO PRIVATE(i) SHARED(n) do i = 1, 200 call some_work(i) end do !$OMP END DO end subroutine phase2 integer :: j !$OMP DO PRIVATE(j) SHARED(n) do j = 1, 100 call more_work(j) end do !$OMP END DO end program par !$OMP PARALLEL call phase1 call phase2 !$OMP END PARALLEL end program par |
次に示す孤立宣言子の使用規則が適用されます。
ワークシェアリング構造内で実行された集合操作 (ワークシェアリング構造または BARRIER) は無効です。
集合操作 (ワークシェアリング構造または BARRIER) を同期化領域内 (CRITICAL/ORDERED) から実行することはできません。
宣言子ペアである開始および終了宣言子 (例えば DO と END DO など) は、プログラムの 1 つのブロック内になければなりません。
変数のプライベート・スコーピングは、ワークシェアリング構造で指定することができます。共有されるスコーピングは、並列領域で指定する必要があります。
OpenMP を使用するためにコードを準備するには、次の段階と手順に従ってください。一般的に、最初の 2 つの段階は単一プロセッサー・システムまたはマルチプロセッサー・システムのいずれでも行うことができますが、その後の段階は通常、デュアルコア・プロセッサー・システムおよびマルチプロセッサー・システムで行います。
OpenMP 並列宣言子を挿入する前に、次の方法でコードが安全に並列実行されることを確認してください。
ローカル変数をスタックに配置します。-openmp (Linux* および Mac OS* X) または /Qopenmp (Windows*) が指定されると、インテル® コンパイラーはデフォルトでこの操作を行います。
-automatic または -auto-scalar (Linux および Mac OS X)、/automatic (Windows) を使用してローカル変数を自動 (automatic) にします。-openmp (Linux および Mac OS X)、/Qopenmp (Windows) コンパイラー・オプションが指定されると、インテル® コンパイラーはデフォルトでこの操作を行います。ローカル変数のスタックへの割り当てを妨げる -save (Linux および Mac OS X)、/Qsave (Windows) オプションを使用しないでください。デフォルトでは、automatic ローカル変数はスレッド間で共有されるため、同期化コードを追加して、スレッドが正常にアクセスできるようにする必要があります。
解析の主な手順は次のとおりです。
プログラムのプロファイルを作成して、最も多くの時間が費やされている箇所を見つけます。この箇所は、並列処理の恩恵が最も得られるプログラムの部分です。この段階は、基本的な PGO オプションを使用して行うことができます。
プログラムが入れ子されたループを含む場合は、常に繰り返し間の依存関係が少ない、最外ループを選択します。
OpenMP を正しく実装するためにプログラムを再編成するには、次のいくつか、またはすべてを実行します。
選択したループが繰り返しを並列に実行できる場合、このループに PARALLEL DO 構造を取り入れます。
アルゴリズムを書き直して、繰り返し間の依存関係を取り除くようにします。
依存関係にかかわる変数の使用と割り当ての周囲に CRITICAL 構造を配置し、残りの繰り返し間の依存関係を同期化します。
ループに存在する変数を適切な SHARED、PRIVATE、LASTPRIVATE、FIRSTPRIVATE、または REDUCTION 節内にリストします。
並列ループの DO インデックスを PRIVATE としてリストします。この手順はオプションです。
グローバルスコープが保持される場合は、共通ブロックの要素を PRIVATE リストに配置してはなりません。THREADPRIVATE 宣言子を使用して、それらの変数をグローバルスコープに持つ共通ブロックを、各スレッドに対してプライベート化します。THREADPRIVATE は、チーム内の各スレッド用に共通ブロックのコピーを作成します。
並列領域のあらゆる I/O を同期化します。
より多くの並列ループを特定し、それらを再編成します。
可能な場合は、隣接する PARALLEL DO 構造を、複数の DO 宣言子が含まれる 1 つの並列領域にマージし、実行のオーバーヘッドを減らします。
チューニング・プロセスでは、SCHEDULE 節または OMP_SCHEDULE 環境変数を使用して、クリティカル・セクションのシーケンシャル・コードを最小化し、負荷のバランスを図ります。
この手順は通常、デュアルコア・プロセッサー・システムおよびマルチプロセッサー・システムで行います。