Sun Studio 12: Fortran プログラミングガイド

10.3 明示的な並列化

この節では、どのループを並列化するか、どの方針を使用するかを明示的に指示するための、f95 によって認識されるソースコード指令について説明します。

Fortran 95 コンパイラは、OpenMP Fortran API を基本並列化モデルとして完全にサポートするようになりました。詳細は、『OpenMP API ユーザーズガイド』を参照してください。

従来の Sun 形式と Cray 形式の並列化指令は、SPARC プラットフォームの Sun Studio コンパイラでサポートされなくなりました。また、x86 プラットフォームのコンパイラでは受け入れられません。

プログラムを明示的に並列化するためには、アプリケーションコードの事前解析と深い理解、そして、共有メモリー並列化の概念が必要です。

DO ループに並列化のためのマークを付けるには、ループの直前に指令を置きます。OpenMP Fortran 95 指令が認識されて DO ループが並列化されるようにするには、-openmp を使用してコンパイルします。並列化指令は、その指令後の DO ループを並列化する (または並列化しない) ようにコンパイラに伝えるコメント行です。指令は、プラグマともいいます。

どのループに並列化のマークを付けるかを選択するときは注意してください。並列を実行するときに間違った結果を計算してしまうデータ依存性がループにある場合でも、コンパイラは、DOALL 指令でマークを付けられたすべてのループに対して、スレッド化された並列コードを生成します。

libthread プリミティブを使用して独自のマルチスレッド化コーディングを行う場合は、コンパイラのいかなる並列化オプションも付けてはいけません。コンパイラは、スレッドライブラリへのユーザーの呼び出しを使用してすでに並列化されているコードを並列化することはできません。

10.3.1 並列可能なループ

次のような場合、ループは明示的な並列化に適しています。

10.3.1.1 スコープ規則: 非公開と共有

非公開変数または専用配列は、ループの 1 回の反復だけで使用されます。ある反復で非公開変数または非公開配列に代入された値は、そのループの別の反復には伝達されません。

共有変数または共有配列は、ほかのすべての反復で共有されます。ある反復で共有変数または共有配列に代入された値は、そのループの別の反復からも参照されます。

明示的に並列化されたループで共有の値を参照する場合、共有によって正確性の問題が発生しないように注意してください。共有変数が更新またはアクセスされたとき、コンパイラは同期処理を行いません。

あるループの中で変数が非公開であると指定した場合、さらに、その変数の唯一の初期化がほかのループの中にある場合、その変数の値はループの中で未定義のままとなる可能性があります。

10.3.1.2 ループでの副プログラム呼び出し

ループで (または呼び出し元ルーチン内から呼び出された副プログラムで) 副プログラムを呼び出すと、データ依存性が生じる可能性があり、これは呼び出しのチェーンをたどってデータや制御フローを深く分析しなければ気づかないでしょう。作業量の多い一番外側のループを並列化すればよいのですが、これらは副プログラムをいくつも呼び出してループがとても深くなっている傾向があります。

このような手続き間の分析は難しく、またコンパイル時間がかなり長くなってしまうので、自動並列化モードでは行われません。明示的な並列化では、コンパイラは、PARALLEL DO または DOALL 指令によりマークしたループ内にサブプログラムへの呼び出しが含まれていても、そのループの並列化コードを生成します。この場合も、ループ内に、また呼び出し先サブプログラムを含めてループ内のすべてにおいてデータ依存が存在しないようにすることはプログラマの仕事です。

さまざまなスレッドから 1 つのルーチンを何度も起動すると、局所静的変数への参照でお互いに干渉し合うような問題が発生することがあります。ルーチン内のすべての局所変数を静的変数ではなく自動変数にすることで、この問題は防ぐことができます。このようにしてサブプログラムを起動すると、そのたびに局所変数が固有の領域に保存され、それらがスタック上で保守されるので、何度起動してもお互いに干渉することはなくなります。

局所サブプログラム変数は、自動変数にすることが可能で、AUTOMATIC 文で指定するか、または -stackvar オプションを指定してサブプログラムをコンパイルすることでスタック上に常駐させることができます。ただし、DATA 文で初期化された局所変数については、実際の割り当てで初期化されるように書きかえる必要があります。


注 –

局所変数をスタックに割り当てると、スタックがオーバーフローしてしまう可能性があります。スタックのサイズを大きくする方法については、「10.1.6 スタック、スタックサイズ、並列化」を参照してください。


10.3.1.3 明示的並列化の抑制

一般に、ユーザーがコンパイラにループを並列化するように明示的に指示している場合、コンパイラはそのようにします。ただし、例外もあり、ループによってはコンパイラが並列化を行わないものがあります。

次に、DO ループの明示的な並列化を妨げる抑制の中で、検出可能なものを示します。

-vpara および -loopinfo を指定してコンパイルすると、コンパイラが明示的にループを並列化している最中に問題を検出すると診断メッセージが発せられます。

次に、一般にコンパイラにより検出される並列化の問題を示します。

表 10–3 明示的な並列化時の問題

問題 

並列化されます 

警告メッセージ 

ループは、並列化されている別のループ内に入れ子にされています。 

いいえ 

いいえ 

ループは、並列化されたループの本文内で呼び出されているサブルーチン内にあります。 

いいえ 

いいえ 

フロー制御文で、ループから外部へのジャンプが許可されています。 

いいえ 

はい 

ループの添字変数が、悪影響を受けています。 

はい 

いいえ 

ループ内の変数に、ループ繰越の依存があります。 

はい 

はい 

ループ内の入出力文 — 通常、出力順序は予想できないので賢明な処理ではありません。

はい 

いいえ 

例: 入れ子にされたループ


      ...
!$OMP PARALLEL DO
      do 900 i = 1, 1000      !  並列化されます (外側のループ)
        do 200 j = 1, 1000    !  並列化されません。警告も発しません
            ...
200   continue
900      continue
      ...

例: サブルーチン内で並列化されたループ


 program main
      ...
!$OMP PARALLEL DO
      do 100 i = 1, 200      <- 並列化されます
        ...
        call calc (a, x)
        ...
100      continue
      ...
subroutine calc ( b, y )
      ...
!$OMP PARALLEL DO
      do 1 m = 1, 1000       <- 並列化されません
        ...
1      continue
      return
      end

この例では、サブルーチン自体が並列で実行されているので、その中のループは並列化されません。

例: ループから外部へのジャンプ


!$omp parallel do
      do i = 1, 1000     ! <- 並列化されず、エラーとなります
        ...
        if (a(i) .gt. min_threshold ) go to 20
        ...
      end do
20      continue
      ...

並列化のマークが付いたループの外にジャンプがあると、コンパイラはエラーと診断します。

例: ループ依存性を持つループの変数


demo% cat vpfn.f
      real function fn (n,x,y,z)
      real y(*),x(*),z(*)
      s = 0.0
!$omp parallel do private(i,s) shared(x,y,z)
      do  i = 1, n
          x(i) = s
          s = y(i)*z(i)
      enddo
      fn=x(10)
      return
      end
demo% f95 -c -vpara -loopinfo -openmp -O4 vpfn.f
"vpfn.f", line 5: Warning: the loop may have parallelization inhibiting reference
"vpfn.f", line 5: PARALLELIZED, user pragma used

ループは並列化されますが、可能なループ依存性は警告中で診断されます。しかし、ループ依存性のすべてがコンパイラによって診断できないことに注意してください。

10.3.1.4 明示的並列化での入出力

並列に実行するループで入出力を実行できます。 ただし、次の条件があります。

例: ループ内の入出力文


!$OMP PARALLEL DO PRIVATE(k)
      do i = 1, 10     !  並列化されます
        k = i
        call show ( k )
      end do
      end
      subroutine show( j )
      write(6,1) j
1      format(’Line number ’, i3, ’.’)
      end
demo% f95 -openmp t13.f
demo% setenv PARALLEL 4
demo% a.out

Line number 9.
Line number 4.
Line number 5.
Line number 6.
Line number 1.
Line number 2.
Line number 3.
Line number 7.
Line number 8.

ただし、入出力が再帰的な場合、つまり、入出力文に、入出力を行う関数への呼び出しが含まれている場合は、実行時エラーが発生します。

10.3.2 OpenMP 並列化指令

OpenMP は、マルチプロセッサプラットフォーム用の並列プログラミングモデルで、Fortran 95、C、C++ のアプリケーションの標準的なプログラミング方法となってきているものです。Forte Developer コンパイラでは、この並列プログラミングモデルを推奨しています。

OpenMP 指令を有効にするには、-openmp オプションフラグを使用してコンパイルします。Fortran 95 OpenMP 指令は、指令名と従属句の前に付く、コメントのような !$OMP という符号によって識別されます。

!$OMP PARALLEL は、プログラム内の並列領域を識別します。!$OMP DO は、並列領域内で並列化すべき DO ループを識別します。この 2 つの指令を組み合せて 1 つの !$OMP PARALLEL DO 指令とすることができます。 この指令は、DO ループの直前に配置します。

OpenMP の仕様には、プログラムの 1 つの並列領域内で作業を共有および同期化するための多数の指令と、データのスコープ指定および制御のための従属句が含まれています。

OpenMP 指令と古い Sun 形式の指令のもっとも大きな違いは、OpenMP では、非公開または共有のいずれかとして明示的にデータのスコープを指定する必要があることです。ただし、自動スコープ指定機能が提供されています。

Sun や Cray の並列化指令を使用して古いプログラムを変換するためのガイドラインも含めて、詳細は、Forte Developer の『OpenMP API ユーザーズガイド』を参照してください。