読者です 読者をやめる 読者になる 読者になる

fortran66のブログ

fortran について書きます。

反復と射

正月なので随想をば思いつくままに書きます。

Modern Fortran において留意すべき点として、FORTRAN77 まではソースコード上で区別されていなかった繰り返し構造を、概念的に大きく二つに分けて記述できる点にあると思います。その二つの区分の呼び名として適当な言葉が思い浮かばないので、とりあえず反復と射ということにしておきます。

反復と射

反復とは、普通の do loop 構造にあたるもので、loop 変数に従った順序付の繰り返し構造にあたります。

do i = 1, n
  a(i + 1) = a(i) + b(i)
end do

このように漸化式の形を取るものは、 i の順番に依存して実行結果が変わってくるので、i が 1 から始まって n まで順番に実行されることが意味を持ちます。

これに対して、ここで射と呼んでいるものは(写像のつもりなんですが)、本来対応を示しているもので、複数の操作をまとめて一気に実行できることを意味しているつもりです。

例として以下で indx が添え字の置換を表しているとして、置換にあたる操作は、

do i = 1, n
  b(i) = a(indx(i))
end do

のように、漸化式の反復の場合と同じような do loop で記述できますが、i の順番に依らずに同じ結果が得られます。したがって i が露わに記述される理由はないことになります。また代入の実行は順番を入れ替えても、並行して行っても構わないはずです。

Fortran90での代入

Fortran90 では、このような添え字に依存しない代入の繰り返しを記述すのに、

b = a(indx)

という記法が導入されました。

条件節のある場合

条件節による分岐がある場合、FORTRAN77では以下の様に記述されます。

do i = 1, n
  if (a(i) > 0) then
    b(i) =  sqrt( a(i) )
  else
    b(i) = -sqrt( a(i) )
  end if
end do

この場合も loop 変数 i には露わに依存性はありませんし、各々の i に対する代入は、順不同・同時・並列に実行してよいはずです。

where 構文

この場合には Fortran90 では where 構文を用いて記述します。

where (a > 0.0)
  b =  sqrt(a)
else where
  b = -sqrt(a)
end where

これによって、添え字への依存性がなく順不同・同時・並列に実行してよいことが明示されます。

forall 構文

似たような状況で loop 変数に露わに依存するものの、順不同に実行できるくりかえし構造もあります。

do i = 1, n
  ia(i) = i * (i - 1) / 2
end do

このような場合 Fortran90 では forall 構文を使います。

forall (i = 1:n)
  ia(i) = i * (i - 1) / 2
end forall

これは、代入が i に関して順不同・同時・並列に実行してよいことを明示的に表現しています。

mask による分岐

条件節による分岐がある場合を、次に示します。

do i = 1, n
  if ( abs( ia(i) ) > 1.0e3  ) ia(i) = ia(i) / i  
end do

forall 構文では mask が使えます。

forall (i = 1:n,  abs(ia(i)) > 1.0e3)
  ia(i) = ia(i) / i
end forall

forall 構文には mask 条件を満たさない場合の else 節にあたるものはありません。

do concurrent 構文

forall は代入操作しかできないため、Fortran2008 では類似の構文として do concurrent 構文が導入されました。

do i = 1, n
  x = i * 0.01
  a(i) = x * log(x) / (x - 1.0)
end do  

は、以下の様に書き直されます。

do concurrent (i = 1:n)
  x = i * 0.01
  a(i) = x * log(x) / (x - 1.0)
end do

これによって、do loop 内の構造が順不同・同時・並列に実行できることが明示されます。

forall の注意点

ちなみに、forall 構文での複数行処理は、各行を完全に実行してから次の行に進みます。つまり

ia(1:n + 1) = -1
forall (i = 1:n)
  ia(i) = i
  ib(i) = i * ia(i + 1)
end forall

ia(1:n + 1) = -1
forall (i = 1:n) ia(i) = i
forall (i = 1:n) ib(i) = i * ia(i + 1)

と等価になります。したがって ib(i) = i * (i + 1) となり -i とはなりません。

まとめ

このように、FORTRAN77 の同じ do loop 構文の記述が、異なる複数の概念を表しています。これらを明示的に区別することで、プログラムの意味をよりはっきりできます。そしてこれはコンパイラによる最適化や並列化の助けとなります。

Fortran コンパイラは賢いので簡単な do loop などの並列性は簡単に見抜きますし、あえて明示的に区別しても対して利益はないかもしれませんが、将来的には必ず有用になると(正月気分で)想像します。

そもそも順番に実行すべき反復動作と、対応関係に基づいて複数の操作をまとめて実行することは別の抽象であるので、そのことを意識的・自覚的に認識して、別の構文で明示的に書き表すのが好ましいように思われます。

Modern Fortran には、そのための構文がいくつも用意されています。