匿名希望のおでんFortranツヴァイさん太郎

生き物、Fortran、川について書く

pack関数の使い方

Fortranの配列用組み込み関数の中にpack関数というものがあります.直観的に配列を扱うことができ,データをまとめたりする際に大変便利な関数です

pack関数は配列xの要素の内,条件を満たす要素(正確にはmaskで指定した論理型配列の成分が真である部分の要素*1)のみを取り出して新しい配列を作る関数です.このように書いてもわかりにくいので,具体的に使い方を示します.

以下のような魚の調査をしたとしましょう.

  • Station 1からStation 10において,ある魚種の体長を調べる
  • 各Stationの底質が砂(Sand)か砂利(Gravel)かを記録する

この調査から以下ようなデータ(data.csv)が得られます.なお,全て架空データです.
このデータから,最小体長,最大体長,平均体長,標準偏差を,調査個体全体,砂場の個体のみ,砂利場の個体のみの三つの区分で算出します.

入力データ

Station, River_bed, TL(cm)
      1,     Sand ,   10.0 
      1,     Sand ,   17.2 
      1,     Sand ,   19.3 
      1,     Sand ,   17.4 
      1,     Sand ,   18.5 
      2,   Gravel ,   13.1 
      2,   Gravel ,   18.8 
      2,   Gravel ,   18.9 
      3,   Gravel ,   11.8 
      3,   Gravel ,   19.6 
      4,     Sand ,   11.7 
      4,     Sand ,   17.3 
      4,     Sand ,   14.0 
      4,     Sand ,   18.0 
      5,     Sand ,   20.1 
      5,     Sand ,   22.0 
      6,     Sand ,   16.0 
      7,   Gravel ,   17.3 
      7,   Gravel ,   16.5 
      7,   Gravel ,   12.0 
      8,     Sand ,   19.8 
      8,     Sand ,   16.7 
      8,     Sand ,   14.9 
      9,   Gravel ,   18.4 
      9,   Gravel ,   13.1 
     10,     Sand ,   13.6 
     10,     Sand ,   11.5 
     10,     Sand ,   15.9 


プログラム

module stat_calc
implicit none

contains

subroutine input(unit_num, file_name, st, bed, tl)
	integer, intent(in)::unit_num
	character(*), intent(in)::file_name
	integer, allocatable, intent(out)::st(:)
	character(*), allocatable, intent(out)::bed(:)
	double precision, allocatable, intent(out)::tl(:)
	!    Loop
	integer line_counter
	integer i

	open(unit_num, file=file_name, status="old", action="read")
	
	line_counter = 0
	read(unit_num, '()') ! ヘッダー行読み飛ばし
	do
		read(unit_num, *, end=99) 
		line_counter = line_counter + 1
	end do
	99 continue
	
	rewind(unit_num) ! ファイルを開きなおして,一行目から読み直し

	allocate(st(line_counter))
	allocate(bed(line_counter))
	allocate(tl(line_counter))

	read(unit_num, '()') ! ヘッダー行読み飛ばし
	do i = 1, line_counter
		read(unit_num, *) st(i), bed(i), tl(i)
	end do

	close(unit_num)
end subroutine


!    平均を求める
function dmean(x) result(rslt)
	double precision, intent(in)::x(:)
	!    Temporary
	double precision rslt

	rslt = dble(sum(x)) / dble(size(x))
end function


!    標準偏差を求める
function dsd(x) result(rslt)
	double precision, intent(in)::x(:)
	!    Temporary
	double precision var
	double precision rslt

	var = dot_product(x - dmean(x), x - dmean(x)) / dble(size(x))
	rslt = sqrt(var)
end function

end module


program main
use stat_calc
implicit none
!    Input
integer, allocatable::st(:)           ! 調査地点番号
character(len=6), allocatable::bed(:) ! 底質
double precision, allocatable::tl(:)  ! 全長
!    Temporary
double precision, allocatable::sand_tl(:)   !    Sandの場のtlだけを集めたもの
double precision, allocatable::gravel_tl(:) !    Gravelの場のtlだけを集めたもの
integer n_sand   ! 砂場の総データ数
integer n_gravel ! 砂利場の総データ数

call input(17, "data.csv", st, bed, tl)

!    packした時の大きさを取得
!    pack関数の書き方は,pack(packされるもの,maskの条件式)
!    bed=="Sand"により,bedと同じ大きさで,"Sand"の部分に相当する要素が.true.,
!    "Gravel"に相当する要素が.false.である論理型配列が指定される
!    bed=="Gravel"の場合も同様に論理型配列を指定している
n_sand = size(pack(tl, bed=="Sand"))     
n_gravel = size(pack(tl, bed=="Gravel")) 

allocate(sand_tl(n_sand))
allocate(gravel_tl(n_gravel))

sand_tl(:) = pack(tl, bed=="Sand")     ! Sandの部分のtlを抽出
gravel_tl(:) = pack(tl, bed=="Gravel") ! Gravelの部分のtlを抽出

print*, "砂場のデータ数  ", n_sand
print*, "砂利場のデータ数", n_gravel

open(17, file="summary.csv", status="replace")
write(17, '(3(A, ","), A)') "", "Total", "Sand", "Gravel"
write(17, '(A, ",", 2(i3, ","), i3)')     "No_individual",  size(tl), n_sand, n_gravel
write(17, '(A, ",", 2(f5.1, ","), f5.1)') "Min_TL"     , minval(tl) , minval(sand_tl), minval(gravel_tl)
write(17, '(A, ",", 2(f5.1, ","), f5.1)') "Max_TL"     , maxval(tl) , maxval(sand_tl), maxval(gravel_tl)
write(17, '(A, ",", 2(f5.2, ","), f5.2)') "Ave_TL"     ,  dmean(tl) ,  dmean(sand_tl),  dmean(gravel_tl)
write(17, '(A, ",", 2(f5.2, ","), f5.2)') "SD_TL"      ,    dsd(tl) ,    dsd(sand_tl),    dsd(gravel_tl)
close(17)

end program

画面出力

 砂場のデータ数            18
 砂利場のデータ数          10

出力ファイル

              , Total,  Sand, Gravel 
 No_individual,    28,    18,      10
        Min_TL,  10.0,  10.0,    11.8
        Max_TL,  22.0,  22.0,    19.6
        Ave_TL, 16.19, 16.33,   15.95
         SD_TL,  3.08,  3.14,    2.96


今回のデータでは区分は底質だけですが,実際の調査ではもっと多くの類型にわけられます.例えば,植物の有無や他魚種の有無でデータを類型化し,類型ごとに今回のような基本統計量やヒストグラムの作成をすることがしばしばあります.

このような時にpack関数によっていじりたいデータだけを抽出すると,プログラムが読みやすく,しかも必要に応じて変更しやすいものになります.

*1:maskや論理型についてはこちら y-tis.hatenablog.com

余白を作らずにepsからpngに変換する

計算やデータ解析の結果を図にする時,私はeps形式で出力します.
図を文書に貼る時は,LaTeXならpdfに変換して貼り*1MS-Wordではpngにして貼ります.

図の変換にはPDF24を使うことが多いのですが,このソフトでepsをpngにするとA4サイズで出力されるため余白ができ,トリミングが面倒です.
特に,複数のグラフを並べる時には,大きさをそろえるのが面倒です.
epsのバウンディングボックスを反映させるか,せめて余白を除いてpngにしたいところです.

そこで,以下のような手続きを踏みます.

  1. PDF24でpdfに変換.
  2. 変換したpdfファイルをInkscapeで開く.
  3. PNG画像にエクスポート」で描画全体を選択し,エクスポートする.

これで描画部分以外の余白が取り除かれたpng画像になります.
ただし,この方法はpdfへの変換を経由するため面倒です.他にもよい方法があると思います.

*1:epsのままだとコンパイルの度にpdfへ変換するため時間がかかる.図をあらかじめpdfにしておくと変換する必要がないため,コンパイルが速い.

ナベブタムシを撮る

ナベブタムシという水生昆虫をご存知でしょうか.
カメムシ目ナベブタムシ科の水生昆虫で,こんな形をしています.
f:id:y_tis:20180724215502j:plain:w400
f:id:y_tis:20180724215701j:plain:w400
上が幼虫,下が成虫です.
成虫でも大きさは10 mm弱です.砂礫底で網を使えば採集できます.
名前が面白いので観察会などで名前を教えると喜ばれます.

さされると痛いらしいです.

写真はいずれも先日撮影したものです.
採集したことは何度もありますが,潜っている最中に見つけたのはこれが初めてです.
撮影機材はオリンパスのTG4です.

カワヨシノボリという魚

Fortranのことばかり書いているので,今回は生き物について書こうと思います.

今回の主役はカワヨシノボリ(学名Rhinogobius flumineus)です.
f:id:y_tis:20180707001959j:plain:w400
f:id:y_tis:20180707001232j:plain:w400

ヨシノボリという種類の魚は他にも日本に生息しており,中には一度海に下って再び河川に戻ってくる種類もいますが,カワヨシノボリは一生を河川で過ごします.

河川によってピークは異なると思いますが,5月から7月くらいにかけて繁殖します.以下の写真のような,黄色の卵を石の裏に産み付けます.成熟が進むと目が見えるようになり,卵はすこし褐色を帯びたような色になります.
f:id:y_tis:20180707000040j:plain:w400
f:id:y_tis:20180707004416j:plain:w400

少し砂に埋まりつつ(はまり石),下に少し隙間が空いているような石をめくると卵が見つかることが多いです.というのも,彼らは石の下を少し掘り起こして隙間を作って産卵します.卵を産んだ後は雄が卵の世話をします.川那部・水野(1989)*1の602,603ページにカワヨシノボリが石の下に穴を掘る過程をとらえたとてもよい写真があるので,興味のある方はぜひ見てみてください.あんな写真を野外で撮ってみたいものです.

仔稚魚は砂州の水際(水深数cm)で見つかります.小さい個体で7 mm位です.分かりにくいですが,以下の写真の真ん中に稚魚がいます.
f:id:y_tis:20180707002811j:plain:w400

比較的写真がとりやすい魚だと思います.水中で使えるカメラを初めて使うような方にはおすすめの生き物です.

*1:川那部浩哉・水野信彦(編)(1989)日本の淡水魚.山と渓谷社

日本の淡水魚 (山渓カラー名鑑)

日本の淡水魚 (山渓カラー名鑑)

optional引数と論理型変数を利用して柔軟な関数を作る

配列にデータ(生物の個体数や水位など)を格納して平均を求める場合,doループを使ってもよいですが,Fortranを使うなら配列用の組み込み手続きを活用したいものです.

大きさ10の整数値配列xの成分を

x(:) = (/1, 2, 3, 4, 5, 6, 7, 8, 9, 10/)

で与えます.この時,配列の成分の和を返す関数sumと配列の成分の個数を返す関数sizeを使って,

dble(sum(x)) / dble(size(x))

でxの平均を算出できます.dble()は倍精度実数への変換です.sumは与える配列が実数なら実数型を返しますが,sizeは必ず整数値を返しますので,dbleを忘れずにつけなければなりません.


このような簡単なデータなら特に問題は起こりませんが,

  • ある年は個体数調査ができなかった
  • 設置していた水位計が流されてしまった

などの理由でデータが欠損している場合もあります.

欠損値に-1をあてることにして,整数値配列yを

y(:) = (/1, 2, 3, 4, 5, -1, 7, 8, 9, -1/)

で与えましょう.-1を避けて平均を算出するとなると,xの場合と同じようには計算できません.

sumにはmaskを指定できます.-1以外の成分の個数を数えるにはcount関数(条件を満たす成分の個数を返す)が使えますので,

dble(sum(y, mask=y > -1)) / dble(count(y > -1)) ! mask= y /= -1, count(y /= -1)でも可

としてyの平均を与えることができます.

以上の計算をまとめて,欠損値がある場合とない場合で平均の計算法を変える関数を定義しましょう.このような場合,optional引数を定義すると使いやすい関数となります.

optional属性を持たせた引数は省略可能になります.欠損値をoptional引数とすることで,欠損値がある時とない時で平均値の演算を使い分ける柔軟な関数になります.書き方は以下通りです.

function iaverage1(x, undef) result(rslt)
	integer, intent(in)::x(:)
	integer, optional, intent(in)::undef
	!    Temporary
	double precision rslt

	if (present(undef)) then ! optional引数が存在する時
		rslt = dble(sum(x, mask=x > undef)) / dble(count(x > undef))
	else ! optional引数が存在しない時
		rslt = dble(sum(x)) / dble(size(x))
	end if
end function

以下注釈です.

  • 配列を引数にするときは,x(:)のように大きさをコロンで指定することで,任意の大きさの配列を指定することができます
  • intent(in)属性をつけることで,その引数は入力としてのみ使い,値を書き換えたりする用途には使わないことを宣言したことになります(この例だと,x(:)に値を加えたりするとコンパイラがエラーを出してくれるようになり,意図しない演算によるバグを防ぐことができます)
  • 関数の名前にaverage1という風にaverageの前にiをつけているのは,引数x(:)が整数型(integer)であることを明示的にするためです


さて,上記の関数ではy < undef)やy /= undefのような場合に対応できません.

  • countで指定する条件はsumのmaskで指定する条件と同じ
  • countの条件もsumのmaskも論理型配列で指定できる

この二点を利用して以下のようにiaverage1を拡張できます.

function iaverage2(x, mask) result(rslt)
	integer, intent(in)::x(:)
	logical, optional, intent(in)::mask(:)
	!    Temporary
	double precision rslt

	if (present(mask)) then
		rslt = dble(sum(x, mask=mask)) / dble(count(mask))
	else
		rslt = dble(sum(x)) / dble(size(x))
	end if
end function

とすることで,状況に応じて柔軟に対応できます.


以上のまとめとして,以下のコードを示します.

module stat_calc
implicit none

contains

function iaverage1(x, undef) result(rslt)
	integer, intent(in)::x(:)
	integer, optional, intent(in)::undef
	!    Temporary
	double precision rslt

	if (present(undef)) then
		rslt = dble(sum(x, mask=x > undef)) / dble(count(x > undef))
	else
		rslt = dble(sum(x)) / dble(size(x))
	end if
end function


function iaverage2(x, mask) result(rslt)
	integer, intent(in)::x(:)
	logical, optional, intent(in)::mask(:)
	!    Temporary
	double precision rslt

	if (present(mask)) then
		rslt = dble(sum(x, mask=mask)) / dble(count(mask))
	else
		rslt = dble(sum(x)) / dble(size(x))
	end if
end function

end module


program main
use stat_calc
implicit none

!    Loop
integer i
!    Parameter
integer, parameter::n = 10
integer, parameter::x(n) = (/(i, i=1, n)/)
integer, parameter::y(n) = (/(i, i=1, 5), -1, (i, i=7, 9), -1/)
logical masking(n)

print'(A, 10i4)', "x(:) = ", x(:)
print'(A, 10i4)', "y(:) = ", y(:)
print*,

!    sumは配列の各成分の和を返す(mask指定可能)関数
!    sizeは配列の要素数を返す(mask指定不可)関数
!    countは条件を満たす要素数(.true.の個数)を返す関数
print*, "Average of x = ", dble(sum(x)) / dble(size(x))
print*, "Average of y (y > -1) = ", dble(sum(y, mask=y > -1)) / dble(count(y > -1))
print*,

!    上記の計算をiaverage1として定義
print*, "iaverage1(x) = ",  iaverage1(x)
print*, "iaverage1(y, -1) = ",  iaverage1(y, -1)
print*,

!    countの条件は論理型変数で指定可能
masking(:) = y > -1
print*, "masking(:) = ", masking(:)
print*, "Average of y using masking = ", dble(sum(y, mask=masking)) / dble(count(masking))
print*,

!    上記の計算をiaverage2として定義
print*, "iaverage2(x) = ",  iaverage2(x)
print*, "iaverage2(y, masking) = ",  iaverage2(y, masking)

end program


結果

x(:) =    1   2   3   4   5   6   7   8   9  10
y(:) =    1   2   3   4   5  -1   7   8   9  -1

 Average of x =    5.5000000000000000     
 Average of y (y > -1) =    4.8750000000000000     

 iaverage1(x) =    5.5000000000000000     
 iaverage1(y, -1) =    4.8750000000000000     

 masking(:) =  T T T T T F T T T F
 Average of y using masking =    4.8750000000000000     

 iaverage2(x) =    5.5000000000000000     
 iaverage2(y, masking) =    4.8750000000000000     

(2018.07.13追記
論理型変数についてはこちらの記事を参照してください.
y-tis.hatenablog.com

論理型(logical)変数の使い方

Fortranでは変数の型として

  • 整数型 integer
  • 実数型 real
  • 倍精度実数型 double precision
  • 複素数型 complex
  • 倍精度複素数型 complex(kind(0d0))
  • 論理型 logical
  • 文字型 character

が使えます.今日の話題は論理型(logical)変数です.

長い間これはどんな場合に使う変数なのか理解できませんでした.簡単な計算ばかりしているときにはありがたみが全く分からなかったのです.1と0で代用できるような場面ばかりだとまず使うことがありませんでした.

データの加工などをするときにはpack関数などのmaskを伴う配列関数をよく使います.このmaskの指定に論理型が使えます.統計処理をするようになってからこの手の関数を使うことが多くなり,やっと論理型変数のありがたみが分かりました.

かんたんな例を挙げましょう.細かい説明はコメントアウトを読んでください.

program main
implicit none

!    Loop
integer i
!    Parameter
integer, parameter::n = 10
integer, parameter::x(n) = (/(i, i=1, n)/)
integer, parameter::y(n) = (/1, 200, 3, 400, 5, 600, 7, 800, 9, 1000/)
logical masking(n)


!    論理型の値は.true.または.false.
!    true, falseの前後のピリオドを忘れないように
masking(:) = .true.
print*, masking(:)
masking(:) = .false.
print*, masking(:)
print*, 


!    pack関数はこのように使う
!    材料となる配列xの成分の内,mask=で指定した条件がtrueの部分に
!    相当する成分だけをまとめた配列を作る
!    従って,maskの条件はxに関する条件でなくてもよい(以下の配列yによるmaskがその例)
print'(A31, 5(" "), 10i3)', "pack(x, mask=mod(x(:), 2) == 1)", pack(x, mask=mod(x(:), 2) == 1)
print'(A31, 5(" "), 10i3)', "pack(x, mask=y(:) < 10)", pack(x, mask=y(:) < 10)

!    pack関数のmask=は省略可能
print'(A31, 5(" "), 10i3)', "pack(x, mod(x(:), 2) == 1)", pack(x, mod(x(:), 2)==1)
print'(A31, 5(" "), 10i3)', "pack(x, y(:) < 10)", pack(x, mask=y(:) < 10)
print*, 


!    maskやif文などで書く論理式は.true.または.false.を返すので,実はprint文でこのように出力できる
!    なお,以下では配列であることを明示的にするためx(:)と書いている
!    それぞれ,mod(x, 2) == 1,x < 5,x >= 5と書いてもよい
print'(A17, 5(" "), 10L3)', "mod(x(:), 2) == 1", mod(x(:), 2) == 1
print'(A17, 5(" "), 10L3)', "x(:) < 5", x(:) < 5
print'(A17, 5(" "), 10L3)', "x(:) >= 5", x(:) >= 5
print*, 


!    pack関数はmask=の値が.true.の部分の要素を集めるので,論理型配列で指定可能
masking(:) = mod(x, 2) == 1
print'(A21, 5(" "), 10i3)', "x(:)", x(:)
print'(A21, 5(" "), 10L3)', "masking(:)", masking(:)
print'(A21, 5(" "), 10i3)', "pack(x, mask=masking)", pack(x, mask=masking)
print*, 

masking(:) = y(:) < 10
print'(A21, 5(" "), 10i3)', "x(:)", x(:)
print'(A21, 5(" "), 10L3)', "masking(:)", masking(:)
print'(A21, 5(" "), 10i3)', "pack(x, mask=masking)", pack(x, mask=masking)

end program


出力結果

 T T T T T T T T T T
 F F F F F F F F F F

pack(x, mask=mod(x(:), 2) == 1)       1  3  5  7  9
        pack(x, mask=y(:) < 10)       1  3  5  7  9
     pack(x, mod(x(:), 2) == 1)       1  3  5  7  9
             pack(x, y(:) < 10)       1  3  5  7  9

mod(x(:), 2) == 1       T  F  T  F  T  F  T  F  T  F
         x(:) < 5       T  T  T  T  F  F  F  F  F  F
        x(:) >= 5       F  F  F  F  T  T  T  T  T  T

                 x(:)       1  2  3  4  5  6  7  8  9 10
           masking(:)       T  F  T  F  T  F  T  F  T  F
pack(x, mask=masking)       1  3  5  7  9

                 x(:)       1  2  3  4  5  6  7  8  9 10
           masking(:)       T  F  T  F  T  F  T  F  T  F
pack(x, mask=masking)       1  3  5  7  9


以上の例のように,trueかfalseをもとにmaskするというのが重要であり,便利なところです.条件によってmaskの内容が異なる場合には,このような論理型変数が威力を発揮します.


(2018.07.13追記
論理型変数を利用して関数を作った例はこちら.
y-tis.hatenablog.com

(2018.08.11追記
pack関数を使うときにも同様の考え方をします.
y-tis.hatenablog.com

ifとcaseと分岐処理削除

Fortranでは分岐処理にifとcaseがあります.条件分岐が三つ以上ある場合,if文では条件を満たすまで上から順にelse ifをたどるのに対し,case文では値の評価をした後,条件を満たすところにジャンプするため,分岐が多い場合にはcaseの方が速いそうです.私は統計処理をする際,文字列を用いた分岐に対してはcaseを使うことが多いです(一文字目が"A"の時に処理aをするなど).

チューニングの際は,まず条件分岐自体を無くせないかを考えます.意外とifやcaseを使わなくても書けるものです.やたらに条件分岐が多いコードを書いている間は,アルゴリズム自体の理解が浅いことが多いです.

今回はdo loopのカウンターの値に応じて処理を変える場合の計算速度をif文,case文,条件分岐を使わない場合で比較します.実用上このような場面は多く,例えば,

  • 移流方程式を時間幅dt=0.1でt=0からt=10まで解き,t=1ごとに解を出力したいとき
  • マルコフ連鎖モンテカルロ法で10回ごとにサンプリングを行いたいとき

などです.

以下がソースコードです.

program main
implicit none
!    Parameter
integer, parameter::iter = 10**8
integer, parameter::thin1 = 10
integer, parameter::thin2 = 5
integer, parameter::thin3 = 2
integer, parameter::thin4 = 1
!    Loop
integer i, j
!    Temporary
integer s
integer m
! Time
integer t0
integer t1 
integer t_rate
integer t_max


s = 0
call system_clock(t0)
do i = 1, iter
	m = mod(i, thin1)
	if (m == 0) then
		s = 0
	else if (m == thin4) then
		s = s + thin4
	else if (m == thin3) then
		s = s + thin3
	else if (m == thin2) then
		s = s + thin2
	else
		s = s * 2
	end if
end do
call system_clock(t1, t_rate, t_max)
print*, calc_time(t0, t1, t_rate, t_max)


s = 0
call system_clock(t0)
do i = 1, iter
	m = mod(i, thin1)
	select case(m)
		case (0)
			s = 0
		case (thin4)
			s = s + thin4
		case (thin3)
			s = s + thin3
		case (thin2)
			s = s + thin2
		case default
			s = s * 2
	end select
end do
call system_clock(t1, t_rate, t_max)
print*, calc_time(t0, t1, t_rate, t_max)


s = 0
i = 1
call system_clock(t0)
do while (i <= iter)
	! mod(i, thin1) = thin4 = 1
	s = s + thin4
	i = i + 1

	! mod(i, thin1) = thin3 = 2
	s = s + thin3
	i = i + 1

	! mod(i, thin1) = 3--4
	do j = thin3+1, thin2-1
		s = s * 2
		i = i + 1
	end do

	! mod(i, thin1) = thin2 = 5
	s = s + thin2
	i = i + 1

	! mod(i, thin1) =  6--9
	do j = thin2+1, thin1-1
		s = s * 2
		i = i + 1
	end do

	! mod(i, thin1) = 0
	s = 0
	i = i + 1
end do
call system_clock(t1, t_rate, t_max)
print*, calc_time(t0, t1, t_rate, t_max)


contains 

function calc_time(t0, t1, t_rate, t_max) result(rslt)
	integer, intent(in)::t0
	integer, intent(in)::t1
	integer, intent(in)::t_rate
	integer, intent(in)::t_max
	!    Temporary
	double precision rslt
	integer diff

	if (t1 < t0) then
		diff = t_max - t0 + t1 + 1
	else
		diff = t1 - t0
	end if
	
	rslt = dble(diff)/dble(t_rate)
end function

end program

結果

  0.39000000000000001     
  0.34399999999999997     
  0.18800000000000000     

若干caseの方が速いですが,あまり変わりませんね.もう少し差が出ると思っていました.条件分岐がない場合は二倍くらいの速度で計算が終わりました.

計算結果の出力を伴う場合,どれくらい差が出るでしょうか.計算部分を以下のように書き直します.三つ目の計算では,write文を書く位置はiのカウントを増やす前であることに注意しましょう.

open(17, file="ex.csv", status="replace")
s = 0
call system_clock(t0)
do i = 1, iter
	m = mod(i, thin1)
	if (m == 0) then
		s = 0
		write(17, *) i, ",", s
	else if (m == thin4) then
		s = s + thin4
		write(17, *) i, ",", s
	else if (m == thin3) then
		s = s + thin3
		write(17, *) i, ",", s
	else if (m == thin2) then
		s = s + thin2
		write(17, *) i, ",", s
	else
		s = s * 2
	end if
end do
call system_clock(t1, t_rate, t_max)
print*, calc_time(t0, t1, t_rate, t_max)

write(17, *) 

s = 0
call system_clock(t0)
do i = 1, iter
	m = mod(i, thin1)
	select case(m)
		case (0)
			s = 0
			write(17, *) i, ",", s
		case (thin4)
			s = s + thin4
			write(17, *) i, ",", s
		case (thin3)
			s = s + thin3
			write(17, *) i, ",", s
		case (thin2)
			s = s + thin2
			write(17, *) i, ",", s
		case default
			s = s * 2
	end select
end do
call system_clock(t1, t_rate, t_max)
print*, calc_time(t0, t1, t_rate, t_max)

write(17, *) 

s = 0
i = 1
call system_clock(t0)
do while (i <= iter)
	! mod(i, thin1) = thin4 = 1
	s = s + thin4
	write(17, *) i, ",", s
	i = i + 1

	! mod(i, thin1) = thin3 = 2
	s = s + thin3
	write(17, *) i, ",", s
	i = i + 1

	! mod(i, thin1) = 3--4
	do j = thin3+1, thin2-1
		s = s * 2
		i = i + 1
	end do

	! mod(i, thin1) = thin2 = 5
	s = s + thin2
	write(17, *) i, ",", s
	i = i + 1

	! mod(i, thin1) = 6--9
	do j = thin2+1, thin1-1
		s = s * 2
		i = i + 1
	end do

	! mod(i, thin1) = 0
	s = 0
	write(17, *) i, ",", s
	i = i + 1
end do
call system_clock(t1, t_rate, t_max)
print*, calc_time(t0, t1, t_rate, t_max)

close(17)


結果

  0.93700000000000006     
  0.93799999999999994     
  0.92200000000000004     

理由はよくわかりませんが,三者ともほとんど同じ速度になりました.条件分岐のない計算が遅くなったのはなぜでしょうか.今回はここまでとします.