-autopar オプションを使用すると f95 コンパイラは、効率的に並列化できる DO ループを自動的に見つけます。このようなループは変形され、利用可能なプロセッサに対してその反復が均等に分配されます。コンパイラは、このために必要なスレッド呼び出しを生成します。
コンパイラによる依存性の解析は、DO ループを並列化可能なタスクに変形します。コンパイラは、ループの構造を変形して、逐次実行する、並列化できないセクションを切り離します。次に、利用可能なプロセッサに対して作業を均等に分配します。各プロセッサが反復の異なったブロックを実行します。
たとえば、4 つの CPU と 1,000 回の反復を持つ並列化ループの例で、各スレッドは 250 回の反復をまとめて実行します。
プロセッサ 1 が実行する反復 |
1 |
から |
250 |
プロセッサ 2 が実行する反復 |
251 |
から |
500 |
プロセッサ 3 が実行する反復 |
501 |
から |
750 |
プロセッサ 4 が実行する反復 |
751 |
から |
1000 |
並列化できるのは、計算の実行順序に依存しないループだけです。コンパイラによる依存性の解析は、本質的にデータ依存性を持つループを拒否します。ループ中のデータフローを完全に決定できない場合、コンパイラは保守的に動作し、並列化を行いません。また、パフォーマンスの向上よりもオーバーヘッドが勝る場合、ループを並列化しないことを選択する可能性もあります。
コンパイラは常に、静的ループスケジューリング (つまり、ループ中の作業を単純に均等な反復ブロックに分割する方法) を使用して、ループを並列化することを選択することに注意してください。明示的な並列化指令を使用すれば、ほかの分配スキームも指定できます。 この指令については、この章の後半で説明します。
例: 配列とスカラー
dimension a(10) real m(100,10), s, u, x, z equivalence ( u, z ) pointer ( px, x ) s = 0.0 ... |
m と a は両方とも配列変数です。s は純スカラーです。変数 u、x、z、px はスカラー変数ですが、純スカラーではありません。
反復間データ依存性をもたない DO ループは、-autopar によって自動的に並列化されます。自動並列化のための一般的な基準は次のとおりです。
明示的な DO ループと、IF ループや Fortran 95 配列構文などの暗黙的なループのみが、並列化されます。
ループの各反復に対する配列変数の値は、そのループのほかの反復に対する配列変数の値に依存してはいけません。
ループ内の計算は、ループの終了後に参照される純スカラー変数を条件によって変更してはいけません。
ループ内の計算は、反復にまたがるスカラー変数を変更してはいけません。これは「ループ伝達の依存性」と呼ばれます。
ループの本文内の処理量は、並列化のオーバーヘッドよりも多くなければいけません。
コンパイラは、コンパイルされたコードを変形するときに、ループ中のデータ依存の原因になりそうな (見かけの) 参照を自動的に取り除きます。このような多数の変換の 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-1 c(i,j) = a(j+1) + 2.3 end do end do end |
前述の例では、外側のループが並列化され、別々のプロセッサ上で実行されます。配列 a を参照する内側のループはデータ依存性の原因になるように見えますが、コンパイラはその配列の一時的な専用コピーを作成して、外側のループの反復を依存しないようにしています。
自動並列化では、次のいずれかが発生すると、コンパイラはループを並列化しません。
DO ループが、並列化される別のループ内の入れ子になっているとき
フロー制御で、DO ループの外に飛び出す可能性があるとき
ループ内で、ユーザーレベルの副プログラムが起動されているとき
ループ内に入出力文があるとき
ループ内の計算が別名付きスカラー変数を変更するとき
マルチプロセッサシステムでは、もっとも内側のループではなく、ループの入れ子のもっとも外側のループを並列化するのがもっとも効果的です。並列処理は一般にループのオーバーヘッドがかなり大きいため、もっとも外側のループを並列化することでループのオーバーヘッドが最小になり、各プロセッサの処理量が最大になります。自動並列化では、コンパイラは入れ子のもっとも外側のループからループの解析を始め、並列化可能なループが見つかるまで、内側に進んでいきます。入れ子の中でループが 1 つでも並列化されたら、並列ループの中に含まれるループは無視されます。
配列をスカラーに変形する計算のことを「縮約操作」と呼びます。典型的な縮約操作は、ベクトルの要素の合計や積です。 縮約操作は、ループ内の計算が反復にまたがって累積的に変数を変更しないという基準には反するものです。
例: ベクトルの要素の合計を縮訳する
s = 0.0 do i = 1, 1000 s = s + v(i) end do t(k) = s |
しかし、一部の操作では、並列化を妨げるのが縮約だけの場合は、この基準にかかわらず並列化できます。共通の縮約操作が頻繁に発生するので、コンパイラはこれらの操作を特別なケースであると認識し、並列化します。
-reduction コンパイラオプションが -autopar か -parallel とともに指定されていなければ、縮約操作の認識は、自動並列化解析の中には含まれません。
並列化可能なループが表 10–2 にリストされた縮約操作のいずれか 1 つを持つ場合、-reduction が指定されていれば、コンパイラはそのループを並列化します。
次の表に、f77 および f95 が認識する縮約操作をリストします。
表 10–2 認識される縮約操作
数学的な操作 |
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 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 関数はすべての形式で認識されます。
次の条件のため、浮動小数点の合計や積の縮約操作が不正確になることがあります。
計算が並列実行されるときの順序が、1 つのプロセッサ上で逐次実行されるときの順序と違う場合
計算の順序が、浮動小数点数の合計や積に影響を与えた場合。ハードウェア浮動小数点の加算や乗算は結合則を満たしません。 どのように演算対象が関連付られているかによって、丸め、オーバーフロー、アンダーフローが発生する可能性があります。たとえば、(X*Y)*Z と X*(Y*Z) は、数値的には意味が違う可能性があります。
状況によって、エラーが受け付けられない場合があります。
例: 丸めの例です。-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% f95 -O4 -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 なので、この乱数のデータは容認できます。詳細は、『数値計算ガイド』を参照してください。