Fortran プログラミングガイド ホーム目次前ページへ次ページへ索引


第 10 章

SPARC: 並列化

この章では、マルチプロセッサの並列化の概要を示し、サンの Fortran コンパイラの機能について説明します。f77f95 との実装の違いについても述べます。


注 - Fortran 並列化機能を使用するには、Sun WorkShop HPC ライセンスが必要です。

基本概念

アプリケーションの並列化 (またはマルチスレッド化) とは、マルチプロセッサシステム上で実行できるよう、またはマルチスレッド環境、コンパイルされたプログラムを分散することです。並列化によって、1 つのタスク (DO ループなど) を複数のプロセッサ (またはスレッド) を使って実行できるので、実行速度が上がる可能性があります。

UltraTM 60、EnterpriseTM Server 6500、または Sun Enterprise Server 10000 のようなマルチプロセッサシステム上でアプリケーションプログラムを効率的に実行できるようにするためには、そのアプリケーションプログラムをマルチスレッド化する必要があります。つまり、並列実行できるタスクを識別し、複数のプロセッサまたはスレッドを横にしてその計算を分配するようにプログラムを変更する必要があります。

アプリケーションのマルチスレッド化は、libthread プリミティブを適切に呼び出すことによって、手作業で行うことができます。しかし、膨大な量の解析とプログラムの変更が必要となります。詳細は、Solaris の『マルチスレッドのプログラミング』を参照してください。

Sun コンパイラは、マルチプロセッサシステム上で動作できるようにマルチスレッド化されたオブジェクトコードを自動的に生成できます。Fortran コンパイラは、並列性をサポートする主要な言語要素としての DO ループに焦点をあわせます。並列化は、Fortran ソースプログラムに一切手を加えることなく、ループの計算作業を複数のプロセッサに分配します。

どのループを並列化するか、またそのループをどのように分配するかは、完全にコンパイラに任せることも (-autopar)、ソースコード指令を使用してプログラマが明示的に決定することも (-explicitpar)、その両方を組み合わせることも (-parallel) できます。


注 - 独自の (明示的な) スレッド管理を行うプログラムをコンパイルするときは、コンパイラのどのような並列化オプションも付けてはなりません。明示的なマルチスレッド化 (libthread プリミティブへの呼び出し) は、並列化オプションを付けてコンパイルしたルーチンと組み合わせることはできません。

プログラム中のすべてのループが有効に並列化されるわけではありません。計算作業量の少ないループを並列化すると、(並列タスクの起動と同期に費やされるオーバーヘッドと比べると) 実際には実行が遅くなることもあります。また、安全に並列化できないループもあります。このようなループは、文間あるいは反復間の依存関係のため、並列化すると異なる結果を生成します。

明示的な DO ループとともに暗示的なループ (IF ループと Fortran 95 配列構文など)が、Fortran コンパイラでの自動並列化の対象となります。

Sun WorkShop コンパイラは、安全にそして有効に並列化できる可能性のあるループを自動的に検出できます。しかし、ほとんどの場合、隠れた副作用の恐れがあるので、この解析はどうしても控え目になります (どのループが並列化され、どのループが並列化されていないかは、-loopinfo オプションで表示できます)。ループの前にソースコード指令を挿入することによって、特定のループを並列化するかどうかを明示的に制御できます。しかし、このように明示的に並列化を指定したループによって結果が間違ったとしても、それはユーザーの責任になります。

f77f95 も、Sun 形式と Cray 形式の 2 つの明示的並列化指令をサポートしています。さらに、f95 は OpenMP 1.1 指令や実行時ライブラリルーチンをサポートしています。Fortran の明示的並列化機能については、187ページで説明します。

速度向上 -- 何を期待するか

4 つのプロセッサ上で動作するようにプログラムを並列化した場合、そのプログラムは、1 つのプロセッサ上で動作させるときの約 1/4 の時間で処理できる (4 倍の速度向上になる) と期待できるでしょうか。

おそらく、答えは「ノー」です。プログラムの全体的な速度向上は、並列実行しているコード中で消費される実行時間の割り合いによって厳密に制限されると証明できます (アムダールの法則)。適用されるプロセッサがいくつになろうとも、これは常に真です。事実、並列実行した実行プログラムの合計時間のパーセンテージを p とすると、理論的な速度向上の制限は 100/(100-c) となります。したがって、プログラムの 60% だけが並列実行した場合、プロセッサの数にかかわらず、速度向上は最大 2.5 倍です。そして、プロセッサが 4 つの場合、このプログラムの理論的な速度向上は、最大限の効率が発揮されたと仮定しても、1.8 倍です。4 倍にはなりません。オーバーヘッドを考えると、実際の速度向上はもう少し減ります。

最適化のことを考えると、ループの選択は重要です。プログラムの合計実行時間のほんの一部としか関わらないループを並列化しても、最小の効果しか得られません。効果を得るためには、実行時間の大部分を消費するループを並列化しなければなりません。したがって、どのループが重要であるかを決定し、そこから始めるのが第一歩です。

問題のサイズも、並列実行するプログラムの割合を決定するのに重要な役割を果たし、その結果、速度向上にもつながります。問題のサイズを増やすと、ループの中で行われる作業量も増えます。3 重に入れ子にされたループは、作業量が 3 乗になる可能性があります。入れ子の外側のループを並列化する場合、問題のサイズを少し増やすと、(並列化していないときのパフォーマンスと比べて) パフォーマンスが大幅に向上します。

プログラムの並列化のための手順

次に、アプリケーションの並列化に必要な手順について、極めて一般的な概要を示します。

1. 最適化。適切なコンパイラオプションのセットを使用して、1 つのプロセッサ上で最高のパフォーマンスを得ます。

2. プロファイル。典型的なテストデータを使用して、プログラムのパフォーマンスプロファイルを決定します。最も重要なループを見つけます。

3. ベンチマーク。逐次処理でのテストの結果が正確かどうかを決定します。これらの結果とパフォーマンスプロファイルをベンチマークとして使用します。

4. 並列化。オプションと指令の組み合わせを使用して、並列化した実行可能ファイルをコンパイルし、構築します。

5. 検証。並列化したプログラムを 1つのプロセッサや 1 つのスレッド上で実行し、結果を検査して、その中の不安定さやプログラミングエラーを見つけます ($PARALLEL または $OMB_NUM_THREADS に 1 を設定します。178ページを参照してください) 。

6. テスト。複数のプロセッサ上でさまざまな実行を試し、結果を検査します。

7. ベンチマーク。専用のシステムで、プロセッサの数を変えながらパフォーマンスを測定します。問題のサイズを変化させて、性能の変化を測定します (スケーラビリティ)。

8. 手順 4 から 手順 7 を繰り返す。パフォーマンスに基づいて、並列化スキームを改良します。

データ依存性の問題

すべてのループが並列化できるわけではありません。複数のプロセッサ上でループを並列実行すると、実行している反復の順序が変わる可能性があります。さらに、ループを並列実行する複数のプロセッサがお互いに干渉する可能性もあります。このような状況が発生するのは、ループ中にデータ依存性がある場合です。

データ依存性の問題が発生する場合は、再帰、縮約、間接アドレス指定、データに依存するループが繰り返されています。

再帰

ループのある反復で設定され、後続の反復で使用される変数は、反復間依存性、つまり再帰の原因となります。ループ中で再帰を行う場合は、反復が適切な順序で実行されなければなりません。

   DO I=2,N
      A(I) = A(I-1)*B(I)+C(I)
   END DO

たとえば、上記コードでは、以前の反復中で A(I) 用に計算された値が、現在の反復中で (A(I-1) として) 使用されなければなりません。各反復を並列実行して、1 つのプロセッサで実行したときと同じ結果を生成するためには、反復 I は、反復 I+1 が実行できる前に完了していなければなりません。

縮約

縮約操作は、配列の要素を 1 つの値に縮約します。たとえば、配列の要素の合計を 1 つの変数にまとめる場合、その変数は反復ごとに更新されます。

   DO K = 1,N
     SUM = SUM + A(I)*B(I)
   END DO

このループを並列実行する各プロセッサが反復のサブセットを取る場合、SUM の値を上書きしようとして、各プロセッサはお互いに干渉します。うまく処理するためには、各プロセッサが 1 度に 1 回ずつ合計を実行しなければなりません。しかし、順序は問題になりません。

ある共通の縮約操作は、コンパイラによって、特別なケースであると認識され、処理されます。

間接アドレス指定

ループ依存性は、値が未知である添字によってループの中の添字付けられた配列への格納から発生する可能性があります。たとえば、添字付の配列中に繰り返される値がある場合、間接アドレス指定は順序に依存することがあります。

   DO L = 1,NW
     A(ID(L)) = A(L) + B(L)
   END DO

上記例中、ID 中で繰り返される値は、 A の要素を上書きする原因となります。逐次処理の場合、最後の格納が最終値です。並列処理の場合、順序は決定されていません。使用される A(L) の値 (古い値か更新された値) は、順序に依存します。

データに依存するループ

並列化できるように、ループを書き換えて、データ依存性を取り除くことができます。しかし、大規模な構造の変形が必要になります。

一般的な規則はいくつかあります。

以上が、並列化のための一般的な条件です。コンパイラの自動並列化解析は、さらに、ループを並列化するかどうかを決定するための基準も考慮します。しかし、指令を使用して、明示的にループを並列化させることも可能です。ループの中に並列化の抑制要因が入っていたり、ループが間違った結果を生成する場合でも可能です。

並列オプションと指令についての要約

次の表に、Sun WorkShop 6 f77f95 の並列化に関するコンパイルオプションを示します。

表 10-1   並列化オプション
オプション フラグ
自動 (のみ)
-autopar
自動、縮約
-autopar -reduction
明示(のみ)
-explicitpar
自動、明示
-parallel
自動、縮約、明示
-parallel -reduction
並列化されるループを表示
-loopinfo
明示に関連する警告を表示
-vpara
局所変数をスタックに割り当て
-stackvar
Sun 形式の MP 指令を使用
-mp=sun
Cray 形式の MP 指令を使用
-mp=cray
OpenMP 指令を使用
-mp=openmp
OpenMP 並列化用にコンパイル
-openmp


オプションについての注意

次の表に、f77f95 の並列化指令をリストします。

表 10-2   並列化指令
並列化指令 目的
C$PAR TASKCOMMON
各スレッドの共通ブロックを非公開として宣言
C$PAR DOALL オプションの修飾子 可能であれば、次のループを並列化
C$PAR DOSERIAL
次のループの並列化を抑制
C$PAR DOSERIAL *
ループの入れ子の並列化を抑制


Cray 形式の指令は似ていますが (206ページを参照してください)、C$PAR の変わりに CMIC$ を使用し、また DOALL 指令で別のオプション修飾子を使用します。これらの指令の使用法については、「明示的な並列化」で説明します。また、『Fortran ユーザーズガイド』の付録 E に、これらの指令や Fortran 95 OpenMP を含め、すべての Fortran 指令の詳しい要約を示します。

スレッドの数

PARALLEL (または OMP_NUM_THREADS) 環境変数は、プログラムで使用可能なスレッドの最大数を制御します。環境変数を設定することにより、実行時システムに、プログラムで使用可能なスレッドの最大数が知らされます。デフォルトは 1 です。一般に、PARALLEL 変数または OMP_NUM_THREADS 変数に、ターゲットプラットフォームで使用可能なプロセッサ数を設定します。

次の例で、その設定方法を示します。

demo% setenv PARALLEL 4       C シェル
                              または
demo% PARALLEL=4            Bourne/Korn シェル
demo% export PARALLEL 4

上記例では、PARALLEL 4 に設定することで、プログラムの実行は最大 4 つのスレッドを使用できます。ターゲットマシンが 4 つのプロセッサを利用できる場合、各スレッドはプロセッサ 1 つずつにマップされます。利用可能なプロセッサが 4 つより少ない場合、スレッドのいくつかは他のスレッドと同じプロセッサ上で実行されるので、パフォーマンスは下がります。

SunOS コマンド psrinfo(1M) は、システムで利用可能なプロセッサのリストを表示します。

demo% psrinfo
0   on-line   since 03/18/96 15:51:03
1   on-line   since 03/18/96 15:51:03
2   on-line   since 03/18/96 15:51:03
3   on-line   since 03/18/96 15:51:03

スタック、スタックサイズ、並列化

プログラムの実行は、プログラムを最初に実行したスレッドのためにメインメモリーのスタックを保持し、各ヘルパースレッドのために個々のスタックを保持します。スタックとは、副プログラムの呼び出し時に引数と AUTOMATIC 変数を保持するために使用される一時的なメモリーアドレス空間です。

メインスタックのデフォルトのサイズは、約 8M バイトです。Fortran コンパイラは、通常、局所変数と配列を (スタックにではなく) STATIC として割り当てます。しかし、
-stackvar オプションを使用すると、すべての局所変数と配列をスタックに割り当てます (あたかもそれが AUTOMATIC 変数であるかのように)。-stackvar は並列化とともに使用することを推奨します。なぜなら、ループ中の CALL を並列化するオプティマイザの能力を向上させるからです。-stackvar は、副プログラム呼び出しを持つ明示的に並列化されたループには必須です。-stackvar については、『Fortran ユーザーズガイド』を参照してください。

C シェル (csh) を使用し、limit コマンドにより現在のメインスタックのサイズを表示し、設定します。

demo% limit                      C シェルの例
cputime 制限無し
filesize 制限無し
datasize 2097148 kbytes
stacksize 8192 kbytes            <- 現在のメインスタックのサイズ
coredumpsize 1 kbytes
descriptors 64 
memorysize 制限無し
demo% limit stacksize 65536       <- メインスタックを 64M バイトに設定
demo% limit stacksize
stacksize 65536 kbytes

demo% >limit -a           Korn シェルの例
cputime(seconds)          制限無し
filesize(blocks)          制限無し
datasize(kbytes)          2097148
stacksize(kbytes)         8192
coredumpsize(blockes)     0
descriptors(descriptors)  64 
memorysize(kbytes)        制限無し
demo% ulimit -s 65536
demo% ulimit -s
65536

Bourne シェルまたは Korn シェルの場合、対応するコマンドは ulimit です。

マルチスレッド化されたプログラムの各スレッドは、独自のスレッドスタックを持っています。このスタックは、初期スレッドのスタックと似ています。しかし、スレッド固有のものです。スレッドの PRIVATE 配列と変数 (スレッドに局所的な) は、スレッドスタックに割り当てられます。デフォルトのサイズは 2 メガバイトです。このサイズは、STACKSIZE 環境変数で設定されます。

demo% setenv STACKSIZE 8192          <- スレッドスタックサイズを 
8M バイトに設定 C シェル
			                または
demo% STACKSIZE=8192	               Bourne/Korn シェル
demo% export STACKSIZE 8192

いくつかの並列化された Fortran コードに対しては、スレッドスタックのサイズをデフォルトより大きく設定することが必要になります。しかし、どれくらいの大きさに設定すればいいのかを知る方法はなく、試行錯誤してみるしかありません。特に、専用配列または局所配列が関連する場合はわかりません。スタックのサイズが小さすぎてスレッドが実行できない場合、プログラムはセグメンテーションフォルトで異常終了します。

自動並列化

-autopar オプション と -parallel オプションを使用すると、f77 および f95 コンパイラは、効率的に並列化できる DO ループを自動的に見つけます。このようなループは変形され、利用可能なプロセッサに対してその反復が均等に分配されます。コンパイラは、このために必要なスレッド呼び出しを生成します。

ループの並列化

コンパイラによる依存性の解析は、DO ループを並列化可能なタスクに変形します。コンパイラは、ループの構造を変形して、逐次実行する、並列化できないセクションを切り離します。次に、利用可能なプロセッサに対して作業を均等に分配します。各プロセッサが反復の異なったブロックを実行します。

たとえば、4 つの CPU と 1,000 回の反復を持つ並列化ループの例で、各スレッドは 250 回の反復をまとめて実行します。

プロセッサ 1 が実行する反復 1 から 250
プロセッサ 2 が実行する反復 251 から 500
プロセッサ 3 が実行する反復 501 から 750
プロセッサ 4 が実行する反復 751 から 1000


並列化できるのは、計算の実行順序に依存しないループだけです。コンパイラによる依存性の解析は、本質的にデータ依存性をもつループを拒否します。ループ中のデータフローを完全に決定できない場合、コンパイラは保守的に動作し、並列化を行いません。また、パフォーマンスの向上よりもオーバーヘッドが勝る場合、ループを並列化しないことを選択する可能性もあります。

コンパイラは常に、静的ループスケジューリング (つまり、ループ中の作業を単純に均等な反復ブロックに分割する方法) を使用して、ループを並列化することを選択することに注意してください。明示的な並列化指令を使用すれば、他の分配スキームも指定できます。この指令については、この章の後半で説明します。

配列、スカラー、純スカラー

自動並列化という観点から、2、3 の定義が必要です。

配列とは、最低でも 1 次元で宣言された変数のことです。

スカラーとは、配列でない変数のことです。

純スカラーとは、別名付けされていない (EQUIVALENCE 文や POINTER 文で参照されていない) スカラー変数のことです。

例 : 配列とスカラー

    dimension a(10)
    real m(100,10), s, u, x, z
    equivalence ( u, z )
    pointer ( px, x )
    s = 0.0
    ...

ma は両方とも配列変数です。s は純スカラーです。

変数 u、x、z、px はスカラー変数ですが、純スカラーではありません。

自動並列化の基準

反復間データ依存性をもたない DO ループは、-autopar-parallel によって自動的に並列化されます。自動並列化のための一般的な基準は次のとおりです。

f77: 見かけの依存性

f77 コンパイラは、コンパイルされたコードを変形するときに、依存の原因になりそうな (見かけの) 参照を自動的に取り除きます。このような多数の変換の 1 つは、一部の配列の専用バージョンを使用します。コンパイラがこの処理を行うことができるのは、一般的には、そのような配列が本来のループで一時領域としてのみ使用されていることが判断できる場合です。

例 : -autopar を使用しています。専用配列によって依存が取り除かれます。

    parameter (n=1000)
    real a(n), b(n), c(n,n)
    do i = 1, 1000           <-- 並列化される
      do k = 1, n
        a(k) = b(k) + 2.0
      end do
      do j = 1, n
        c(i,j) = a(j) + 2.3
      end do
    end do
    end

上記の例では、外側のループが並列化され、別々のプロセッサ上で実行されます。配列 a を参照する内側のループはデータ依存性の原因になるように見えますが、コンパイラはその配列の一時的な専用コピーを作成して、外側のループの反復を依存しないようにしています。

自動並列化の抑制要因

自動並列化では、次のいずれかが発生すると、コンパイラはループを並列化しません。

入れ子にされたループ

マルチプロセッサシステムでは、最も内側のループではなく、ループの入れ子の最も外側のループを並列化するのが最も効果的です。並列処理は一般にループのオーバーヘッドがかなり大きいため、最も外側のループを並列化することでループのオーバーヘッドが最小になり、各プロセッサの処理量が最大になります。自動並列化では、コンパイラは入れ子の最も外側のループからループの解析を始め、並列化可能なループが見つかるまで、内側に進んでいきます。入れ子の中でループが 1 つでも並列化されたら、並列ループの中に含まれるループは無視されます。

縮約操作を使用した自動並列化

配列をスカラーに変形する計算のことを「縮約操作」と呼びます。典型的な縮約操作は、ベクトルの要素の合計や積です。縮約操作は、ループ内の計算が反復にまたがって累積的に変数を変更しないという基準には反するものです。

例 : ベクトルの要素の合計を縮訳する

    s = 0.0
    do i = 1, 1000
        s = s + v(i)
    end do
    t(k) = s

しかし、一部の操作では、並列化を妨げるのが縮約だけの場合は、この基準にかかわらず並列化できます。共通の縮約操作が頻繁に発生するので、コンパイラはこれらの操作を特別なケースであると認識し、並列化します。

-reduction コンパイラオプションが -autopar-parallel とともに指定されていなければ、縮約操作の認識は、自動並列化解析の中には含まれません。

並列化可能なループが表 10-3 にリストされた縮約操作のいずれか 1 つを持つ場合、
-reduction が指定されていれば、コンパイラはそのループを並列化します。

認識される縮約操作

次の表に、f77 および f95 が認識する縮約操作をリストします。

表 10-3   認識される縮約操作  
数学的な操作 Fortran 文のテンプレート
合計
s = s + v(i)
s = s * v(i)
ドット積
s = s + v(i) * u(i)
最小 s = amin( s, v(i))
最大
s = amax( s, v(i))
OR
do i = 1, n
	b = b .or. v(i)
end do
AND
b = .true.
do i = 1, n
	if (v(i) .le. 0) b=b .and. v(i)
end do
ゼロでない要素の計数
k = 0
do i = 1, n
	if ( v(i) .ne. 0 ) k = k + 1
end do


MIN 関数と MAX 関数はすべての形式で認識されます。

数値的な正確性と縮約操作

次の条件のため、浮動小数点の合計や積の縮約操作が不正確になることがあります。

状況によって、エラーが受けつけられない場合があります。

例 : 縮約の並列化を指定したときと指定しないときの、オーバーフローとアンダーフローです。

demo% cat t3.f
    real A(10002), result, MAXFLOAT
    MAXFLOAT = r_max_normal()
    do 10 i = 1 , 10000, 2
    A(i) = MAXFLOAT
    A(i+1) = -MAXFLOAT
10  continue

 
    A(5001)=-MAXFLOAT
    A(5002)=MAXFLOAT
 
    do 20 i = 1 ,10002            ! Add up the array
    RESULT = RESULT + A(i)
20  continue
    write(6,*) RESULT
    end
demo% setenv PARALLEL 2          {プロセッサの数は 2 つ}
demo% f77 -silent -autopar t3.f 
demo% a.out
   0.                            {縮約の並列化なし。0. は正しい。}
demo% f77 -silent -autopar -reduction t3.f
demo% a.out
  Inf                            {縮約の並列化あり。Inf は間違い。}
demo% 

例 : 丸めの例です。-1 と +1 の間の 100,000 個の乱数を合計します。

demo% cat t4.f
    parameter ( n = 100000 )
    double precision d_lcrans, lb / -1.0 /, s, ub / +1.0 /, v(n)
    s = d_lcrans ( v, n, lb, ub ) ! n 個の -1 と +1 の間の乱数を求める。
    s = 0.0
    do i = 1, n
    s = s + v(i)
    end do
    write(*, '(" s = ", e21.15)') s
    end
demo% f77 -autopar -reduction t4.f

結果は、プロセッサの数によって異なります。次の表に、-1 と +1 の間の 100,000 個の乱数の合計を示します。

プロセッサの数 出力
1
 s = 0.568582080884714E+02
2
 s = 0.568582080884722E+02
3
 s = 0.568582080884721E+02
4
 s = 0.568582080884724E+02


この状況では、丸めの誤差はおよそ 10-14 なので、この乱数のデータは容認できます。詳細は、『数値計算ガイド』を参照してください。

明示的な並列化

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

Sun WorkShop 6 Fortran コンパイラは、Sun 形式と Cray 形式の並列化指令を受け付けるため、明示的に並列化されたプログラムを他のプラットフォームから移植しやすくなっています。

Fortran 95 コンパイラは、OpenMP の Fortran 並列化指令も受け付けます。OpenMP の Fortran 仕様は、
http://www.openmp.org
にあります。OpenMP 指令、ライブラリルーチン、環境変数については、『Fortran ユーザーズガイド』の付録 E に要約しています。

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

DO ループに並列化のためのマークを付けるには、ループの直前に指令を置きます。DO ループを認識させ、並列コードを生成させるためには、コンパイラオプション -parallel-explicitpar を使用しなければなりません。並列化指令は、指令後の DO ループを並列化する (または並列化しない) ようにコンパイラに知らせるコメント行です。指令は、プラグマともいいます。

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

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

並列可能なループ

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

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

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

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

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

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

ループでのサブプログラム呼び出し

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

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

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

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


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

明示的並列化の抑制

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

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

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

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

表 10-4   明示的な並列化時の問題
問題 並列化 警告メッセージ
ループは、並列化されている別のループ内にネストされています。 いいえ いいえ
ループは、並列化されたループの本文内で呼び出されているサブルーチン内にあります。 いいえ いいえ
フロー制御文で、ループから外部へのジャンプが許可されています。 いいえ はい
ループの添字変数が、悪影響を受けています。 はい いいえ
ループ内の変数に、ループ繰越の依存があります。 はい はい
Iループ内の I/O 文―通常、出力順序は予想できないので賢明な処理ではありません。 はい いいえ


例: ネストされたループ

      ...
C$PAR DOALL
      do 900 i = 1, 1000      !  並列化します(外側のループ)
        do 200 j = 1, 1000    !  並列化しません、警告も発しません
            ...
200   continue
900      continue
      ...

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

      program main
      ...
C$PAR DOALL
      do 100 i = 1, 200
        ...
        call calc (a, x)
        ...
100      continue
      ...
 
      subroutine calc ( b, y )
      ...
C$PAR DOALL
      do 1 m = 1, 1000
        ...
1      continue
      return
      end
ループ処理 100 が並列で実行されます。
ループ処理 1 は並列で実行されませ
ん。


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

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

C$PAR DOALL
      do i = 1, 1000     !  並列化されません、警告が発せられます
        ...
        if (a(i) .gt. min_threshold ) go to 20
        ...
      end do
20      continue
      ...

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

C$PAR DOALL
      do 100 i = 1, 200        ! 並列化されます、警告が発せられます
        y = y * i              !  y にはループ繰越依存があります
        a(i) = y
100      continue
      ...

明示的並列化での入出力

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

例: ループ内の入出力文

C$PAR DOALL
      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 -explicitpar -vpara t13.f
demo% setenv PARALLEL 2
demo% a.out
(出力には 1 から 10 までの数字が表示されますが、これは確定的な順序ではありません)

例: 再帰的な入出力

      do i = 1, 10       <--  並列化されます、警告は発せられません
(安全ではありません)
        k = i
        print *, list( k )    <-- list は入出力を実行する関数です
      end do
      end
      function list( j )
      write(6,"('Line number ', i3, '.')") j
      list = j
      end
 demo% f95 -mt t14.f
 demo% setenv PARALLEL 2
 demo% a.out


この例で、プログラムは libF77_mt でデッドロックとなり、停止してしまう可能性があります。キーボード制御を取り戻すには Ctrl+C キーを押します。

並列化されたループ内で入出力が発生しても、プログラマがそれに気づかない場合があります。たとえば、算術例外 (ゼロによる除算など) が発生したときに出力を印刷する例外ハンドラをユーザーが指定していたとします。並列化されたループで例外が発生すると、ハンドラからの暗黙の入出力が原因で入出力のデッドロックが発生し、システムが停止してしまう可能性があります。

一般に、次のことがあてはまります。

非公式の定義ですが、次の場合、インターフェイスは MT 安全です。

データレースは、メモリのアドレスの内容が複数のスレッドにより更新されようとしており、またそのアドレスがロックにより保護されていない場合に発生します。そのため、そのメモリアドレスの値は不確定となります。2 つのスレッドがスレッドを更新しようとして競合します (ただし、この場合後からスレッドにたどり着いた方が勝者となります)。

マルチスレッド法を使用してパフォーマンスがよくなるように実装が調整されている場合、インタフェースは通常 MT 活用と呼ばれます。詳細については、Solaris の「マルチスレッドのプログラミング」を参照してください。

Sun 形式の並列化指令

Sun 形式の指令は、-explicitpar オプションや -parallel オプションを指定してコンパイルした場合に、デフォルトで (または -mp=sun オプションを指定して) 使用できます。

Sun 並列化指令の構文

並列化指令は、1 つまたは複数の指令行で構成されます。Sun 形式の指令行は次のように定義されます。

C$PAR Directive    [ Qualifiers ]                <- 初期指令行
C$PAR& [More_Qualifiers]                        <- オプションの継続行

Sun 形式の並列化指令は、次のとおりです。

指令 動作
TASKCOMMON
COMMON ブロックの変数をスレッド非公開として宣言する。
DOALL
次のループを並列化する。
DOSERIAL
次のループを並列化しない。
DOSERIAL*
次のループの入れ子を並列化しない。


サン形式の並列化指令の例

C$PAR TASKCOMMON ALPHA                  ブロックを非公開として宣言
      COMMON /ALPHA/BZ,BY(100)

 
C$PAR DOALL                             修飾子なし

 
C$PAR DOSERIAL

 
C$PAR DOALL SHARED(I,K,X,V), PRIVATE(A)  
            この 1 行の指令は、次の 3 行の指令と同じ意味である。
C$PAR DOALL
C$PAR& SHARED(I,K,X,V)
C$PAR& PRIVATE(A)

TASKCOMMON 指令

TASKCOMMON 指令は、グローバルな COMMON ブロックの変数をスレッド非公開として宣言します。共通ブロックで宣言した変数はすべてスレッドに対して非公開変数になりますが、スレッド内ではグローバルなままです。指定した COMMON ブロックだけが TASKCOMMON として宣言できます。

指令の構文は次のとおりです。

C$PAR TASKCOMMON comon_block_name

指令は、その指定されたブロックの COMMON 宣言の直前または直後に指定しなければなりません。

この指令が有効になるのは、-explicitpar または -parallel オプションを付けてコンパイルしたときだけです。それ以外の場合では、この指令は無視され、ブロックは通常の共通ブロックとして扱われます。

TASKCOMMON ブロックで宣言した変数は、すべての DOALL ループや、DOALL ループ内から呼び出されているルーチンでスレッド非公開変数として処理されます。各スレッドはそれぞれ COMMON ブロックのコピーを取得するので、あるスレッドにより書き込まれたデータはその他のスレッドから直接参照することはできません。プログラムの連続部分では、最初のスレッドの COMMON ブロックコピーがアクセスされます。

TASKCOMMON ブロックの変数は、PRIVATESHAREDREADONLY などの DOALL 修飾子では使用されません。

ブロックが定義されているコンパイルユニットのうちすべてではないが、いくつかでは、共通ブロックをタスク共通として宣言するとエラーになります。-commonchk=yes フラグを付けてプログラムをコンパイルすることで、タスク共通整合性の実行時検査を行うことができます。(実行時検査は、パフォーマンスを下げることのできるプログラム開発の段階だけで行ってください。)

DOALL 指令

DOALL 指令は、コンパイラにその直後に続く DO ループを並列化するコードを生成するように要求します (-parallel オプションまたは -explicitpar オプションを指定してコンパイルした場合)。


注 - ループが明示的に並列化されている場合、そのループ内の縮約操作の解析と変形は行われません。

例 : ループの明示的な並列化

demo% cat t4.f
    ...
C$PAR DOALL
    do i = 1, n			           
       a(i) = b(i) * c(i)
    end do
    do k = 1, m			          
       x(k) = x(k) * z(k,k)
    end do
    ...
demo% f77 -explicitpar t4.f

DOALL の修飾子

DOALL 指令のすべての修飾子はオプションです。表 10-4 にそれらを要約します。

表 10-5   DOALL の修飾子
修飾子 動作 構文
PRIVATE
変数 u1u2、... を反復間で共有しない。
DOALL PRIVATE(u1,u2,...)
SHARED
変数 v1v2、... を反復間で共有する。
DOALL SHARED(v1,v2,...)
MAXCPUS
n 個を超える CPU を使用しない。
DOALL MAXCPUS(n)
READONLY
指定の変数を DOALL ループで
変更しない。
DOALL READONLY(v1,v2,... )
SAVELAST
DO ループの最後の反復におけるすべての専用変数の値を保存する。
DOALL SAVELAST
STOREBAC
K
DO ループの最後の反復における変数 v1v2、... の値を保存する。
DOALL 
STOREBACK(v1,v2,...)
REDUCTIO
N
変数 v1、v2、... を縮約変数として扱う。
DOALL 
REDUCTION(v1,v2,...)
SCHEDTYP
E
スケジューリング型を t に設定する。
DOALL SCHEDTYPE(t)


PRIVATE(varlist)

PRIVATE(varlist) 修飾子は、変数リスト varlist 中のすべてのスカラーと配列が DOALL ループの非公開であることを指定します。配列とスカラーは両方とも非公開として指定できます。配列の場合、DOALL ループのスレッドごとに配列全体のコピーが作成されます。DOALL ループで参照されるスカラーや配列のうち、変数リストに含まれないものはすべて、デフォルトのスコープ規則に従います (188ページ参照)。

例: ループ i で配列 a を非公開として指定します。

C$PAR DOALL PRIVATE(a)
    do i = 1, n
      a(1) = b(i)
      do j = 2, n
      a(j) = a(j-1) + b(j) * c(j)
    end do
    x(i) = f(a)
    end do

SHARED(varlist)

SHARED(varlist) 修飾子は、変数リスト varlist 中のすべてのスカラーと配列が DOALL ループにおいて共有されることを指定します。配列とスカラーは両方とも共有として指定できます。共有スカラーと共有配列は、DOALL ループのすべての反復で共通です。DOALL ループで参照されるスカラーや配列のうち、変数リストに含まれないものはすべて、デフォルトのスコープ規則に従います。

例 : 共有変数を指定します。

    equivalence (a(1),y)
C$PAR DOALL SHARED(y)
    do i = 1,n
      a(i) = y
    end do

上記の例では、変数 y は、その値が i ループの反復間で共有される変数であると指定されています。

READONLY(varlist)

READONLY (varlist) 修飾子は、変数リスト varlist 中のすべてのスカラーと配列が DOALL ループにおいて読み取り専用であることを指定します。読み取り専用のスカラーと配列は、DOALL ループ中のどの反復においても変更されないという、共有スカラーと共有配列の特別なクラスです。スカラーや配列を READONLY として指定すると、コンパイラは、DOALL ループの各スレッドごとに、その変数または配列の別々のコピーを使用する必要がないということを、コンパイラに示します。

例 : 読み取り専用変数を指定します。

    x = 3
C$PAR DOALL SHARED(x),READONLY(x)
    do i = 1, n
      b(i) = x + 1
    end do

上記の例では、x は共有変数です。しかし、READONLY が指定されているので、コンパイラは、x の値が I ループの反復においても変更されないことを信頼できます。

STOREBACK (varlist)

STOREBACK 変数または STOREBACK 配列とは、その値が DOALL ループで計算される変数または配列のことです。計算された値は、そのループの終了後に使用できます。言い換えると、ループの最後の反復における STOREBACK スカラーと STOREBACK 配列の値は、DOALL ループの外から参照できます。

例 : ループインデックス変数を STOREBACK として指定します。

C$PAR DOALL PRIVATE(x), STOREBACK(x,i)
    do i = 1, n
      x = ...
    end do
    ... = i
    ... = x

上記の例では、変数 xi は両方とも i ループの非公開変数であり、STOREBACK 変数でもあります。x 値が最後の反復が終わった時点の値であるのに対し、ループの後の i 値は n+1 となります。

STOREBACK には、留意すべきいくつかの潜在的な問題があります。

最後の反復が、STOREBACK 変数または STOREBACK 配列の値を最後に更新する反復と同じ場合でも、STOREBACK 操作は明示的に並列化されたループの最後の反復時に発生します。

例 : STOREBACK 変数は、逐次バージョンとは異なる可能性があります。

C$PAR DOALL PRIVATE(x), STOREBACK(x)
    do i = 1, n
      if (...) then
        x = ...
      end if
    end do
    print *,x

上記の例では、出力される STOREBACK 変数 x の値は、i ループの逐次バージョンで出力された結果と異なる可能性があります。明示的に並列化された場合、i ループの最後の反復(i = n) を処理し、xSTOREBACK 操作を行うプロセッサは、現在 x の最後に更新された値をもっているプロセッサとは異なる可能性があります。コンパイラはこのような潜在的な問題に関する警告メッセージを出します。

SAVELAST

SAVELAST 修飾子は、非公開スカラーと非公開配列のすべてが DOALL ループにおいて STOREBACK であることを指定します

例 : SAVELAST を指定します。

C$PAR DOALL PRIVATE(x,y), SAVELAST 
    do i = 1, n
      x = ...
      y = ...
    end do
    ... = i
    ... = x
    ... = y

この例では、変数 xyiSTOREBACK 変数です。

REDUCTION(varlist)

REDUCTION (varlist) 修飾子は、変数リスト varlist 中のすべての変数が DOALL ループにおいて縮約変数であることを指定します。縮約変数 (または配列) とは、その部分的な値を別々のプロセッサ上で個々に計算し、その部分的な値をもとにして最後の値を計算できる変数のことです。

縮約変数のリストを指定すると、コンパイラが、DOALL ループが縮約ループであるかどうかを識別し、そのループの並列縮約コードを生成するのを助けます。

例 : 縮約変数を指定します。

C$PAR DOALL REDUCTION(x)
    do i = 1, n
      x = x + a(i)
    end do

上記の例では、変数 x は (合計の) 縮約変数です。i ループは (合計の) 縮約ループです。

SCHEDTYPE(t)

SCHEDTYPE(t) 修飾子は、特定のスケジューリング型を指定して DOALL ループをスケジュールすることを指定します。

表 10-6   DOALL SCHEDTYPE の修飾子 
スケジューリング型 動作
STATIC
当該 DO ループに対して、静的スケジューリングを使用する。(これは、f77 でも f95 でも、Sun 形式の DOALL のデフォルトスケジューリング型である。) すべての反復を均一に利用可能なプロセッサに分配する。 例: 反復が 1,000 回で、プロセッサが 4 個の場合、各スレッドは 250 回の連続反復を 1 かたまりとして取得する。
SELF[(chunksize)] 当該 DO ループに対して、自己スケジューリングを使用する。各スレッドは、一度に chunksize 回の反復を 1 かたまりとして取得する。それは、すべての反復が処理されるまで不確定順序で配布される。反復のかたまりは、使用可能なすべてのスレッドに一様に配布されることはない。 · chunksize が指定されない場合、コンパイラは値を選択する。 例: 反復が 1,000 回で、chunksize が 4 の場合、各スレッドはすべての反復が処理されるまで一度に 4 回分の反復を取得する。
FACTORING[( m )] 当該 DO ループに対して、ガイド付き自己スケジューリングを使用する。初期の反復が n 回で、スレッド数が k 個の場合、すべての反復はいくつかの反復かたまりのグループに分けられる。最初のグループには、それぞれが n/(2k) 回の反復が k かたまりだけある。そして、2 番目のグループには n/(4k) 回の反復が k かたまりだけある。異化同様である。各グループのかたまりのサイズは、2k で除算した残りの反復となる。FACTORING は動的なので、各スレッドが各グループから正確に 1 つずつかたまりを取得するとは限らない。

  • 各スレッドに、m 回以上の反復を割り当てなければならない。

  • 最後の 1 回は、余った小さな値でもかまわない。

  • m を指定しない場合、コンパイラにより値が選択される。

    例: 反復が 1,000 回で、FACTORING(3) を指定し、スレッドが 4 個の場合、最初のグループに 125 回の反復を、2 番目のグループに 4 チャンクの 62 回の反復を、そして 3 番目のグループに 4 チャンクの 31 回の反復を、というように割り当てる。
  • GSS[( m )] 当該 DO ループに対して、ガイド付き自己スケジューリングを使用する。 初期の反復が n 回で、CPU が k 個の場合、次のようになる。 

  • 1 番目のプロセッサに m/k 回の反復を割り当てる。

  • すべての反復が処理されるまで、k で除算した残りの反復を 2 番目のスレッドに、というように割り当てる。

    GSS は動的なので、反復かたまりが使用可能なすべてのスレッドに一様に配布されることはない。

  • 各スレッドに m 回以上の反復を割り当てなければならない。

  • 最後の 1 回は、余った小さな値でもかまわない。

  • m を指定しない場合、コンパイラにより値が選択される。

    例: 反復が 1,000 回で、GSS(10) と指定され、スレッドが 4 個の場合、最初のスレッドに 250 回の反復が、2 番目のスレッドに 187 回の反復が、そして 3 番目のスレッドに 140 回の反復が、というように割り当てられる。

  • 複数の修飾子

    修飾子は複数回指定でき、この場合は効果が累積されます。修飾子が衝突する場合は、警告メッセージが出力され、最後に出現する修飾子が優先されます。

    例 : 3 行の Sun 形式の指令です (MAXCPUSSHARED、PRIVATE 修飾子の衝突に注意)。

    C$PAR DOALL MAXCPUS(4) READONLY(S) PRIVATE(A,B,X) MAXCPUS(2)
    
    C$PAR DOALL SHARED(B,X,Y) PRIVATE(Y,Z)
    
    C$PAR DOALL READONLY(T)
    

    例 : 上記 3 行と同じ内容を 1 行で指定します。

    C$PAR DOALL MAXCPUS(2), PRIVATE(A,Y,Z), SHARED(B,X), READONLY(S,T)
    

    DOSERIAL 指令

    DOSERIAL 指令は、指定したループの並列化を無効にします。この指令は、指令の直後にあるループ 1 つだけに適用されます。

    例 : 1 つのループを並列化から除外します。

        do i = 1, n
    
    C$PAR DOSERIAL
    
          do j = 1, n
    
            do k = 1, n
    
              ...
    
            end do
    
          end do
    
        end do
    

    この例では、-parallel を指定してコンパイルすると、j ループは並列化されませんが、i または k ループは並列化されます。

    DOSERIAL* 指令

    DOSERIAL* 指令は、ループの指定した入れ子の並列化を無効にします。この指令は、指令の直後にあるループの入れ子全体に適用されます。

    例 : ループの入れ子全体を並列化から除外します。

        do i = 1, n
    
    C$PAR DOSERIAL*
    
          do j = 1, n
    
            do k = 1, n
    
               ...
    
            end do
    
          end do
    
        end do
    

    上記のループで parallel を用いてコンパイルすると、j のループは並列化されず、i または k ループが並列化されます。

    DOSERIAL*DOALL の相互作用

    DOSERIAL*DOALL の両方が同じループに指定されている場合、最後の指令が使用されます。

    例 : DOSERIALDOALL を両方とも指定します。

    C$PAR DOSERIAL*
    
        do i = 1, 1000
    
    C$PAR DOALL
    
          do j = 1, 1000
    
            ...
    
          end do
    
        end do
    

    上記の例では、i ループは並列化されず、j ループは並列化されます。

    また、DOSERIAL* 指令のスコープは、テキスト上で DOSERIAL* 指令の直後にあるループの入れ子を超えることはありません。DOSERIAL* 指令は、DOSERIAL* 指令がある関数またはサブルーチンに限定されます。

    例 : DOSERIAL* は、呼び出されたサブルーチンのループまで拡張されません。

        program caller
    
        common /block/ a(10,10)
    
    C$PAR DOSERIAL*
    
        do i = 1, 10
    
          call callee(i)
    
        end do
    
        end
    
    
     
    
        subroutine callee(k)
    
        common /block/a(10,10)
    
        do j = 1, 10
    
          a(j,k) = j + k
    
        end do
    
        return
    
        end
    

    上記の例では、サブルーチン callee への呼び出しがインライン化されているかどうかにかかわらず、DOSERIAL*i ループにしか適用されず、j ループには適用されません。

    Sun 形式のデフォルトのスコープ規則

    Sun 形式 (C$PAR) の明示的な指令では、コンパイラはデフォルトの規則を適用して、スカラーや配列が共有か非公開かを判別します。デフォルトの規則を変更するには、ループの中で参照されるスカラーや配列の属性を指定します。Cray 形式の !MIC$ 指令では、ループ内に現れるすべての変数は、DOALL 指令を使用して、共有か非公開用かを明示的に宣言しなければなりません。

    コンパイラは、次のデフォルトの規則を適用します。

    ループに反復間依存性が存在する場合、実行すると間違った結果になる可能性があります。ユーザーは、このような事態が発生しないように注意しなければなりません。コンパイラは、このような状況をコンパイル時に検出し、警告を発することもあります。しかし、コンパイラは、このようなループの並列化を無効にするわけではありません。

    例: 問題が発生する可能性がある equivalence 文

          equivalence (a(1),y)
    
    C$PAR DOALL
    
          do i = 1,n
    
            y = i
    
            a(i) = y
    
          end do
    

    この例では、スカラー変数 ya(1) と等価であるため、デフォルトでこのスカラー変数 y を非公開変数として、また a(:) を共有変数として扱ってしまいます。つまり、並列化された I ループを実行したときに、間違った結果を引き起こす可能性があります。この場合、診断は発行されません。

    この例を修正するには、C$PARDOALLPRIVATE (y) を使用します。

    Cray 形式の並列化指令

    並列化指令には Sun 形式と Cray 形式の 2 つの形式があります。f77f95 のデフォルトは Sun 形式 (-mp=sun) です。Cray 形式の指令を使用する場合は、-mp=cray を付けてコンパイルする必要があります。

    Sun 形式の指令を付けてコンパイルしたプログラム単位と Cray 形式の指令を付けてコンパイルしたプログラム単位を混在させると、異なる結果を生成する可能性があります。

    Sun 形式の指令と Cray 形式の指令の主な違いは、Cray 形式では、ループ中のすべてのスカラーと配列に対して、SHAREDPRIVATE のどちらかによる明示的なスコープの指定が必要であることです。

    次の表に、Cray 形式の指令の構文を示します。

    !MIC$ DOALL
    
    !MIC$&  SHARED( v1, v2,  ... )
    
    !MIC$&  PRlIVATE( u1, u2,  ... )
    
        ...任意の修飾子
    


    Cray 形式の指令の構文

    並列化指令は、1 つまたは複数の指令行から構成されます。指令行は、次の点を除いて、Sun 形式 (194ページ) と同じ構文で定義されます。

    Cray 指令は、Sun 形式と似ています。

    Cray 指令 Sun 形式との比較
    DOALL
    
    修飾子セットとスケジューリングが異なります。
    TASKCOMMON
    
    Sun 形式と同じです。
    DOSERIAL
    
    Sun 形式と同じです。
    DOSERIAL*
    
    Sun 形式と同じです。


    DOALL 修飾子

    Cray 形式の指令では、PRIVATE 修飾子が必要です。DO ループ内の各変数は、非公開または共有として修飾されなければならず、DO ループの添字は常に非公開でなければなりません。次の表に、利用可能な Cray 形式の修飾子を要約します。

    表 10-7   DOALL 修飾子 (Cray 形式)
    修飾子 動作
    SHARED( v1, v2, ... ) 変数 v1v2、... を反復間で共有する。つまり、これらの変数はすべてのタスクからアクセス可能である。
    PRIVATE( x1, x2, ... ) 変数 x1x2、... を反復間で共有しない。つまり、各タスクがこれらの変数の独自のコピーをもつ。
    SAVELAST
    DO ループの最後の反復における非公開変数の値を保存する。
    MAXCPUS( n ) n 個を超える CPU を使用しない。


    Cray 形式の指令では、DOALL 指令は、1 つのスケジューリング修飾子を指定できます (たとえば、!MIC$& CHUNKSIZE(100))。表 10-8 に、Cray 形式の DOALL 指令を示します。

    表 10-8   DOALL Cray スケジューリング
    修飾子 動作
    GUIDED
    ガイド付き自己スケジューリングを使用して、反復を分配する。この分配は、動的な負荷バランスを行うことで同期のオーバーヘッドを最小にする。デフォルトのチャンクサイズは 64 です。GUIDED は、Sun 形式の GSS(64) と同じです。
    SINGLE
    使用可能なスレッドごとに 1 回の反復を配布する。SINGLE は動的であり、Sun 形式の SELF(1) と等価である。
    CHUNKSIZE( n ) 使用可能なスレッドごとに n 回の反復を分配する。 n は、整数の式でなければならない。最高のパフォーマンスを得るためには、n は整数の定数でなければならない。CHUNKSIZE(n) は Sun 形式の SELF(n) と等価である。 例: 反復が 100 回で、CHUNKSIZE(4) の場合、各スレッドに一度に 4 回の反復を分配する。
    NUMCHUNKS( m ) n 回の反復がある場合、利用可能なプロセッサごとに n/m 回の反復を分配する。最後の 1 回は、余った小さい値でもかまわない。m は式である。NUMCHUNK(m) は、Sun 形式の SELF(n/m) と等価である。ただし、n は反復の合計回数である。 例: 反復が 100 回で、NUMCHUNK(4) の場合、各スレッドは一度に 25 回分の反復を取得する。


    f77 でも f95 でも、デフォルトのスケジューリング型は Sun 形式の STATIC です(Cray 形式の DOALL 指令でスケジューリング型が指定されていない場合)。これと等価の Cray 形式のスケジューリング型はありません。

    環境変数

    並列化で使用される環境変数には、次の 3 つがあります。

    (「スタック、スタックサイズ、並列化」 の説明も参照してください)

    PARALLELOMP_NUM_THREADS

    並列化されたプログラムをマルチスレッド環境で実行するには、実行前に、PARALLEL または OMP_NUM_THREADS 環境変数を設定しなければなりません。これにより、実行時システムに、プログラムで作成可能なスレッドの最大数が知らされます。デフォルトは 1 です。一般に、PARALLEL または OMP_NUM_THREADS 環境変数には、ターゲットプラットフォームで使用可能なプロセッサ数を設定します。

    SUNW_MP_THR_IDLE

    プログラムの並列処理を実行する各スレッドのタスク終端ステータスを制御するには、SUNW_MP_THR_IDLE 環境変数を使用します。この変数には、spinsleep nssleep nms のいずれかを設定できます。デフォルトは spin です。この場合、並列タスクの分担の処理が終わったとき、新しい並列タスクが届くまでスレッドはスピン待ちすることを意味します。そのほかの値を選択すると、スレッドは n 秒 (ns) または n ミリ秒 (nms) 間のスピン待ち後にスリープ状態になります。この待ち時間を超える前に新しいタスクが届くと、スレッドはスピン待機を中断し、新しいタスクを開始します。

    % setenv SUNW_MP_THR_IDLE 50ms
    
    % setenv PARALLEL 4
    
    % myprog
    

    この例では、プログラムで多くても 4 個のスレッドが作成されます。並列タスクが終了した後、スレッドは 50 ミリ秒間スピン待機します。その時間内にそのスレッドに新しいタスクが到着すると、スレッドはそのタスクを実行します。それ以外の場合、スレッドは新しいタスクが届くまでスリープ状態に入ります。

    並列化されたプログラムをデバッグする

    並列化されたプログラムをデバッグするには、新たな作業が必要になります。次に、その方法をいくつか示します。

    dbx を使用しないデバッグ

    並列化されたプログラムをデバッグするには、ちょっとした技術が必要です。

    dbx による並列コードのデバッグ

    並列化されたループに dbx を使用するためには、一時的にプログラムを次のように書き直します。

    例 : 並列化されたプログラムで dbx が使用できるように、ループを手作業で変形します。

    オリジナルコード
    
    demo% cat loop.f
    
    C$PAR DOALL
    
        DO i = 1,10
    
        WRITE(0,*) 'Iteration ', i
    
        END DO
    
        END
    
    サブルーチンとして呼び出し元ループとループ本体の 2 つのパートに分割。
    
    demo% cat loop1.f
    
    C$PAR DOALL
    
        DO i = 1,10
    
        k = i
    
        CALL loop_body ( k )
    
        END DO
    
        END
    
    
     
    
    demo% cat loop2.f
    
        SUBROUTINE loop_body ( k )
    
        WRITE(0,*) 'Iteration ', k
    
        RETURN
    
        END
    並列化オプションをつけてコンパイル。デバッグ用オプションはつけない。
    demo% f77 -O3 -c -explicitpar loop1.f
    デバッグ用オプションをつけ、並列化せずコンパイル。
    demo% f77 -c -g loop2.f
    両方を a.out にリンク。
    demo% f77 loop1.o loop2.o -explicitpar
    dbx 制御下で a.out を実行し、ブレークポイントをループ本体のサブルーチンに設 定。
    demo% dbx a.out   ← さまざまな dbx のメッセージはここでは省略。
    
    (dbx) stop in loop_body
    
    (2) stop in loop_body
    
    (dbx) run
    
    Running: a.out 
    
    (process id 28163)
    dbx はブレークポイントで停止。
    t@1 (l@1) stopped in loop_body at line 2 in file
    "loop2.f"
    2 WRITE(0,*) 'Iteration ', k
    k を表示。
    (dbx) print k
    
    k = 1         ← 1 以外のさまざまな値が考えられる。
    
    (dbx)
    


    サン・マイクロシステムズ株式会社
    Copyright information. All rights reserved.
    ホーム   |   目次   |   前ページへ   |   次ページへ   |   索引