インテル® Fortran コンパイラは、OpenMP* ディレクティブを含む Fortran プログラムを入力ファイルとして処理し、マルチスレッド・バージョンのコードを生成します。並列プログラムが実行を開始する際に存在する単一スレッドは、 マスタスレッドと呼ばれます。並列領域に到達するまで、マスタスレッドはシーケンシャルに処理を続行します。
並列領域とは、1 つのスレッドチームによって並列に実行されなければならない 1 ブロックのコードです。OpenMP Fortran API では、並列構造は OpenMP ディレクティブ PARALLEL をコード・セグメントの最初に、そして END PARALLEL を最後に配置することによって定義されます。このように境界のあるコード・セグメントは、並列実行が可能です。
コードの構造ブロックとは、最初と最後に単一の入口/出口ポイントを持つ、1 つまたは複数の実行文の集まりです。
インテル Fortran コンパイラはワークシェアリング構造および同期化構造をサポートします。これらの各構造は、特定の 1 つまたは 2 つの OpenMP ディレクティブと、囲まれた、または後続するコードの構造ブロックから構成されます。構造の詳細な定義については、「OpenMP Fortran version 2.0 仕様」を参照してください。
並列領域の最後で、スレッドはすべてのチームメンバが到達するまで待機します。そして、チームは論理的になくなり (次の並列領域で再利用されることもあります)、マスタスレッドは次の並列領域が検出されるまでシーケンシャルに処理を続行します。
ワークシェアリング構造は、それを囲む並列領域に入る時点で作成されたチームメンバ間で、囲まれたコード領域の実行を分割します。マスタスレッドが並列領域に入ると、スレッドチームが形成されます。並列領域の最初から開始して、ワークシェアリング構造が検出されるまで、コードは複製されます (すべてのチームメンバによって実行されます)。ワークシェアリング構造は、チームのメンバ間で、囲まれたコード領域の実行を分割します。
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
同期化構造および MASTER
同期化とは、共有データの一貫性を保証し、スレッド間の並列実行を調整するスレッド間通信です。データがアクセスされる際にすべてのスレッドが同じ値を取得する場合、スレッドのチームの共有データは一貫しています。同期化構造は、 共有データの一貫性を保証するために使用されます。
OpenMP の同期化ディレクティブには、CRITICAL、ORDERED、ATOMIC、FLUSH および BARRIER があります。
並列領域またはワークシェアリング構造内では、一度に 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 などのディレクティブは、ラインタイム時に、囲まれた並列領域に動的に “バインド” し、プログラム・ユニット内に出現することができます。
孤立ディレクティブは、コードに最小限の変更を行うだけで、既存のコードを並列処理することができます。また、孤立化は 1 つの並列領域を、呼び出されたサブルーチン内にある複数の DO ディレクティブとバインドすることでパフォーマンスの向上を可能にします。次のコード・セグメントを参照してください。
...
!$omp parallel
call phase1
call phase2
!$omp end parallel
...
subroutine phase1
!$omp do private(i) shared(n)
do i = 1, n
call some_work(i)
end do
!$omp end do
end
subroutine phase2
!$omp do private(j) shared(n)
do j = 1, n
call more_work(j)
end do
!$omp end do
end
次に示す孤立ディレクティブの使用規則が適用されます。
1 つの孤立した ワークシェアリング構造 (SECTIONS、SINGLE、DO) は、単一のスレッドからなる 1 つのチームによって実行されます。つまり、順次実行されます。
ワークシェアリング構造内で実行された集合操作 (ワークシェアリング構造または BARRIER) は無効です。
集合操作 (ワークシェアリング構造または BARRIER) を同期化領域内 (CRITICAL/ORDERED) から実行することはできません。
ディレクティブ・ペアである開始および終了ディレクティブ (例えば DO と END DO など) は、プログラムの 1 つのブロック内になければなりません。
変数のプライベート・スコーピングは、ワークシェアリング構造で指定することができます。共有されるスコーピングは、並列領域で指定する必要があります。詳細は、「OpenMP Fortran version 2.0 仕様」を参照してください。
OpenMP を使用するためにコードを準備するには、次の段階と手順に従ってください。一般的に、最初の 2 つの段階は単一プロセッサ・システムまたはマルチプロセッサ・システムのいずれでも行うことができますが、その後の段階は通常、マルチプロセッサ・システムで行います。
OpenMP ディレクティブを挿入する前に
OpenMP 並列ディレクティブを挿入する前に、次の方法でコードが安全に並列実行されることを確認してください:
ローカル変数をスタックに配置します。これは、-openmp が使用されると、インテル Fortran コンパイラはデフォルトで行います。
-automatic または -auto_scalar を使用してローカル変数を自動 (automatic) にします。これは、-openmp が使用されると、インテル Fortran コンパイラはデフォルトで行います。ローカル変数のスタックへの割り当てを妨げる -save オプションを使用しないでください。デフォルト (-auto_scalar) では 、ローカル・スカラ変数はスレッド間で共有されるため、同期化コードを追加して、スレッドが正常にアクセスできるようにする必要があります。
解析
解析の主な手順は次のとおりです:
プログラムのプロファイルを作成して、最も多くの時間が費やされている個所を見つけます。この個所は、並列処理の恩恵が最も得られるプログラムの部分です。この作業を行うには、インテル® VTune™ アナライザまたは基本的な PGO オプションを使用できます。
プログラムがネストされたループを含む場合は、常に繰返し間の依存関係が少ない、最も外側のループを選択します。
再編成
OpenMP を正しく実装するためにプログラムを再編成するには、次のいくつか、またはすべてを実行します。
選択したループが繰返しを並列に実行できる場合、このループに PARALLEL DO 構造を取り入れます。
アルゴリズムを書き直して、繰返し間の依存関係を取り除くようにします。
依存関係にかかわる変数の使用と割り当ての周囲に CRITICAL 構造を配置し、残りの繰返し間の依存関係を同期化します。
ループに存在する変数を適切な SHARED、PRIVATE、LASTPRIVATE、FIRSTPRIVATE、またはREDUCTION 節内にリストします。
並列ループの DO インデックスを PRIVATE としてリストします。この手順はオプションです。
グローバル・スコープが保持される場合は、 COMMON ブロックの要素を PRIVATE リストに配置してはいけません。THREADPRIVATE ディレクティブを使用して、それらの変数をグローバル・スコープに持つCOMMON ブロックを、各スレッドに対してプライベート化します。THREADPRIVATE は、チーム内の各スレッド用に COMMON ブロックのコピーを作成します。
並列領域のあらゆる I/O を同期化します。
より多くの並列ループを特定し、それらを再編成します。
可能な場合は、 隣接する PARALLEL DO 構造を、複数の DO ディレクティブが含まれる 1 つの並列領域にマージし、実行のオーバーヘッドを減らします。
チューニング
チューニング・プロセスでは、SCHEDULE 節または omp_schedule 環境変数を使用して、クリティカル・セクションのシーケンシャル・コードを最小化し、負荷のバランスをはかります。
注
通常、この手順はマルチプロセッサ・システムで実行されます。