首都大学東京 田川研究室
2013年12月11日更新
コラムへ戻る
Introduction
ネットに情報の少ないFortran77に関する情報で、われら研究員が試行錯誤の末に得た知識を整理しています。
わからなければこれを見よ。
さぼりがちですみません。これからはまめに情報を足して行きたいと思います。
Fortran77とは
再初期の高級言語です。
CPUの機能は、基本的に今も昔も変わらず四則演算、条件分岐、メモリ操作です。これだけでPCの全機能を操作できるのですが詳しい話はさておき。 今日のCPUはいわゆるノイマン型コンピュータです。メモリからデータをもってきて、そこに書いてある内容から次の命令を判断し、演算結果をメモリに書き出し…… を永遠に繰り返すマシンをそう呼びます。(たぶん)
これらの機能を抽象的、特にメモリ操作をプログラマーが意識せずにかけるようにしたのがFortranでした。なので条件分岐、実行位置の操作などはほぼCPUの本来の機能がそのまま命令語として実装されています。(Cだとこれもだいぶ抽象化します。)go to 文なんかがその例でしょう。
コンパイラ
Fortranのコンパイラはあまり数は多くないです。代表的なのがIntelのコンパイラです。超高速なコードを出力してくれるのですが値段が高すぎて手が出せません。お金はないけどFortranがやりたいという人には以下をお勧めします。
上記二つを組み合わせれれば自然対流コードくらいなら余裕で組めます。ググればここ数年は普通に出てくると思います。あとはコマンドプロンプト上でディレクトリ移動ができれば大丈夫です。
ちなみにnotepad++ 、Visual Fortran 2008よりは見やすい色分け等をしてくれます。もちろんただのテキストエディタなのでデバッグ機能はありませんが。
命令一覧
Fortran77に搭載されている命令一覧とその機能、ちょっと高度な使い方などをまとめてあります。命令をクリックすると説明の場所へ飛びます。
命令語を小文字で書いたりしていることに意味はありません。Fortranは大文字と小文字を区別しないので、小文字で書いても大丈夫ですよという目安です。
read!
整数型変数を宣言します。
倍精度浮動小数型変数を宣言します。
単精度浮動小数型変数を宣言します(確か)。
サブルーチンを呼び出します。
指定された出力へ書き込みます。
指定された入力から読み出します。
追加の入出力を開きます。ファイルIOのことです。書き込みモード指定例あり
自動変数を定義します。推奨しません
ループをきじゅつします。
aをbで割った余りを求めます。
何も処理をしない行です
定数値を宣言します。
出力フォーマットの説明
プログラムに関係のないコメントを入れる方法があります。
配列の処理は大規模データ解析の基本です。覚えてください。
条件式です。記述が少しわかりづらいですが慣れましょう。
文字列操作ができれば、出力するファイル名を時間ステップによって変えることができます。拡張子もつけれます。
ちょっとした記述でCPUのベクトル演算回路を使うことができます。
自然対流のサンプルコード。VTKBinaryフォーマットで出力します。paraviewで可視化可能です。
移流梶島スキーム 他2次中心 連続の式 HSMAC法
命令詳細およびTopics
注意事項
注意事項。
文字コードはUTF-8(BOMなし)あるいはANSIで保存する。notepad++上にあるメニューのエンコードの中から選べる。メモ帳でも名前を付けて保存するときに選べる。UNICODEで保存するとコンパイラーがメモリエラーを起こすので注意。
命令文の前は必ず空白6個を入れる。タブはダメ。
一行にかける文字数は65文字まで。昔のフォートラン用カードが65文字までしか書けなかった名残。66文字かもしれないがnotepad++では文字数制限をオーバーした文字は緑いろに表示が変わるのでそれで判断する。
もっと一行に書きたい場合は次の行に空白5つ+&を書くとよい。これで行が連結される。
integer ICOUNT
ICOUNT = 2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +10000 +100 +256 + 50 *(10 +22 +0 )-3 *4 +
上記のように長い行のはしがコメントのように認識されなくなる。以下のように書けばよい。
integer ICOUNT
ICOUNT = 2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +10000 +100 +256 + 50 *(10 +22 +0 )-3 *4 +
20 +6 +4 +5
改行を&でつなぐ。これで好きなだけ長い式がかけますよ。
integer
整数型変数の宣言です。以下使用例です。
integer ICOUNT
まあこれだけなんですが。変数宣言は命令実行部の前にすべて書いておかないといけません。コンパイルエラーになるはずです。
ところで、大事なのは整数型変数の特徴ですね。まず高速な点があげられます。そして自然数を数えるのに使います。プログラムはメモリ、つまり配列のインデックスを実行時に内部で計算しているのですがそうした処理はすべて整数で計算されます。PCの動作において整数型の計算が最も重要です。浮動小数計算なんておまけです。最近誤解している人が多いですが整数演算が速いプロセッサが優秀でOSを高速に動作させられます。
double precision
倍精度浮動小数点数型を宣言します。有効数字15,6桁です。
double precision xd
整数演算の項でも書きましたが、CPUのメインの仕事はアドレス計算のための整数演算です。浮動小数はおまけなので、倍精度なんておまけ中のおまけです。よって市販のPCには倍精度演算が省かれがちです。激遅だと思っていいですね。
real
単精度浮動小数点数型を宣言します。(確か)有効数字7,8桁です
real xs
単精度演算は倍精度演算よりも需要があります。皆さんPCでゲームをやりますので。よって単精度演算は市販のCPUでも結構高速に動作します。
なのでどうでもいい計算はできるだけ単精度でやったほうがいいです。例えばMAC法の圧力修正をするとき、序盤は大まかにやってもいいので単精度を使うとか。
call
サブルーチンを呼び出します。アセンブリの名残でしょうか?あまり意味がないおまじないみたいなものだと思っていい気がします。
例、叫ぶサブルーティン
PROGRAM MAKEINPUT
CALL AAAXX
END
SUBROUTINE AAAXX
WRITE (6 , *) "こおぉぉぉぉぉる!"
RETURN
END
call って格好いいですよね。
write
出力します。出力先は柔軟に決めることができデバイス番号で指定します。文字列変数を指定することもできます。6が標準出力で通常モニタになります。
(?? 標準出力!? FortranでCGIを作ることが不可能ではない?)
PROGRAM MAKEINPUT
WRITE (6 , *) "やっほー!"
END
まあみたとおりのプログラムです。
read
入力です。入力先は柔軟に決めることができデバイス番号で指定します。
5が標準入力だったと思います。標準入力とは確かキーボードです。
PROGRAM MAKEINPUT
double precision Ainput
READ (5 , *) Ainput
END
キーボードから数値を受け取ります。文字列を入力したらバグると思います。
open
指定したデバイス番号にファイルを割り当てます。作成したファイルデバイスを使ってwriteで書き込むことができます。
PROGRAM MAKEINPUT
OPEN (30 , FILE="test.txt" )
WRITE (30 , *) "やっほー!"
CLOSE (30 )
END
close でファイルを閉じれば書き込み完了です。デバイス番号として、5,6は標準入出力なので割り当てられないかと思います。
ファイルを作れないディレクトリに作ろうとしたり、べつのプログラムがファイルを編集できないように制御していたり(EXCEL等)すると失敗してエラーとなりますから気を付けてください
なお、上記のコードではモニタに出力する内容がそっくりそのままテキスト形式でファイルに書き込まれますがそのほかにも書き込みの仕方があります。
ファイルの内容に追加書き込み
現在あるファイルを消して新規書き込み
バイナリ形式で書き込み
バイナリ形式の説明は
・バイナリとは?
・VTK Binary出力
・fortran VTK Binary出力
等を参考にしてください。
以下に例を示します。
OPEN (30 , FILE="test.txt" )
OPEN (30 , FILE="test.txt" , position ="append" )
OPEN (30 , FILE="test.txt" , form ="unformatted" access ="stream" )
OPEN (30 , FILE="test.txt" , form ="unformatted" access ="stream"
,convert ="big_endian" ,position ="append" )
big_endianについてはバイナリとは?のページで説明します。対義語はlittle endianです。
implicit
自動変数の定義です。変数の宣言、定義をしなくても変数を使える状態になります。以下のコードを見てください。
IMPLICIT DOUBLE PRECISION (A-H,O-Z)
Ax = 2
上記コードをセットアップに書くと、A~H、O~Zで始まる変数は型の定義、変数の宣言をしなくても自動で倍精度浮動小数の変数とみなされます。
↑に書いてあるように変数 Ax は宣言していなくてもいきなり使える状態になります。
この機能は簡単なプログラムでない限り推奨されません。スペルミスをした場合、新しい変数として自動認識されてしまうのでデバッグしない限り見つからないバグとなってしまいます。以下のコードをお勧めします。
IMPLICIT NONE
Ax = 2
これで自動宣言されることはなく、変数は宣言しないと使えない状態になります。
バグが減ると思います。
do ~ end do
ループを記述します。do ~ end do で囲まれた間がくりかえされます。明らかなループ処理部はgo to を使うよりこちらを使った方がわかりやすいと思います。
integer i,j
j=0
do i=1 ,10
j=j+i
end do
write (6 ,*) , j
これできっと55と出力されることでしょう。
mod(a,b)
aをbで割った余りを求める。
integer i
i=mod (5 ,3 )
iには2が入るでしょう。
continue
何も実行しない行で、からの行との違いは基本ありませんが、go to 分のジャンプ先に指定することができます。
go to 666
write (6 ,*) "あああああ"
write (6 ,*) "あああああ"
write (6 ,"(a4)" ) "あああああ実は4文字しか出力されません!"
write (6 ,*) "実は出力されません!飛ばされたー"
666 continue
parameter
定数値を宣言します。プログラムで何度も使う共通の意味を持った値は定数値で宣言しましょう。
定数の値を変えるだけで済むようになります。
特に、配列の大きさは定数で与えないとコンパイルエラーとなります。配列の大きさはプログラム開始時に決まっていなくてはならず、変数というのはプログラム開始後に
決まる値ですので、当然配列の大きさを初期化することができません。
同じ大きさの配列をいくつも作るでしょうからまとめて定数で与えておけば後で楽になります。
integer , parameter Ax =2 ,B=30
double precision , parameter as=20.115
write (6 ,*) Ax , B , as
フォートランには数値等を出力するときにフォーマットがあり、出力先にきれいに表示させることができます。
(出力要素数)(出力するフォーマット)(出力に使う文字数).(小数点以下の桁数)
のようにかきます。出力要素数は省略可能です。整数、文字列の出力に小数点以下の桁数を指定しても無視される気がします。以下が例
write (6 ,*) "aaあ"
write (6 ,'(I5)' ) 222
write (6 ,'(I5.3)' ) 222
write (6 ,'(F5.3)' ) 222.2
write (6 ,'(I5)' ) 222.2
write (6 ,'(F5.2)' ) 22
write (6 ,'(F5.2)' ) 22.0
write (6 ,'(F5.2)' ) real (22 )
write (6 ,'(I5)' ) int (22.0 )
write (6 ,'(I5,I5,F5.1)' ) int (22.0 ),20 ,20.12
write (6 ,'(2I5,F5.1)' ) int (22.0 ),20 ,20.12
write (6 ,'(2x,a5,2I5,F5.1)' ) "aaあ" ,int (22.0 ),20 ,20.12
!を書くとその行はそれ以降コメントになります。
以下の例で、1行目、2行目の使い方は大丈夫ですが3行目の使い方はエラーになります。
行の初めからコメントにするか、何か実行文の終わり以降をコメントにするかの使い方しかできません。
write (6 ,*) "あああ"
配列 !
配列を使えば、一つの変数名で大量のデータを扱えます。以下例、
double precision aA(0 :9 ),Sa
integer i
do i=0 ,9
aA(i) = i*2
end do
Sa = 0
do i=0 ,9
Sa = Sa + aA(i)
end do
write (6 ,'(F8.2)' ) Sa
90と表示されるはずです。このように配列を使えばループを使ってデータをまとめて処理できます。配列は基本的にループ処理をするときに使うようなもんです。
条件式一覧
Fortranで使う条件式を並べておきます。
A.LT .B
A.LE .B
A.EQ .B
A.NE .B
A.GT .B
A.GE .B
AがBより小さい
AがBより小さいか等しい
AとBが等しい
AとBが等しくない
AがBより大きい
AがBより大きいか等しい
使用例は以下のようになります。
AX = 2
IF (AX.LE .5 )THEN
AX = 5
END IF
上記コードはIFの中の条件が真になるので AX は 5 になります。
こちらでもいいです。
A < B
A <= B
A == B
A != B
A > B
A >= B
AがBより小さい
AがBより小さいか等しい
AとBが等しい
AとBが等しくない
AがBより大きい
AがBより大きいか等しい
使用例は以下のようになります。
AX = 2
IF (AX <= 5 )THEN
AX = 5
END IF
Fortranというのはアセンブリの命令語をそのまま持ってきたものが多いようです。アセンブリを書いていた人から見たらきっととてもわかりやすいのだと思います。
アセンブリというのはFortranより前のプログラミング言語でCPUの電子回路を直接操作するような命令列です。機械語と1対1に対応してます。一度勉強するとCPUの動作が嫌というほどわかります。メモリ周りのバグにも強くなるでしょう。
プログラミング言語の機能を有名どころで並べれば、
アセンブリ <<<< Fortran < C << C++ <= JAVA
のような順で高機能となっています。<の数はあくまで個人的な感想です。 一般に高級言語は遅いといわれ、アセンブリが最速だと思っている人がいますがそんなことはありません。近年のコンパイラーは大変優秀ですので私なんかが書いたアセンブリではダブルスコアをつけられてしまいます。かなり頑張って書いても同じ速度になる程度です。
文字列操作
FORTRAN77は初期の言語であるものの、結構便利な文字列操作機能が標準でついています。これに関してはCの標準よりも扱いやすいです。
以下に文字列操作のサンプルコードを載せます。ファイル名を生成し、ファイルを作成するコードです。数値解析で文字列操作といってもこれくらいあれば十分ですね。
PROGRAM MAKEINPUT
CHARACTER *20 FNAME
INTEGER I
DO 100 , I=0 , 1000
WRITE (FNAME , '(I4,A4)' ) I , '.plt'
OPEN (10 , FILE=FNAME)
CLOSE (10 )
100 CONTINUE
END
実行すれば0.plt~1000.pltの空ファイルが出力されます。これでinputファイルなしで出力ファイル名が決定できますね。
WRITE文の出力先は文字列変数にすることもできるのです。
ベクトル演算による高速化
最近のCPUには必ず積まれているベクトル演算を行う回路があります。SIMDといわれそれを操作する命令群をSSE、AVXなどと呼びます。
倍精度ですと、SSEでは2次元、AVXでは4次元のベクトル演算ができます。詰まるところ2倍、4倍の処理速度が得られるかもしれないわけです。(実際はよくて2倍、だいたい1.4倍とか。)
使うのは簡単で、コード中にベクトルっぽい記述があればコンパイラーが自動で見つけ出してSSEを使うコードを出力してくれます。
例えば以下のコードがあったとします。
real*4 U0 (1 :100 )
real*4 V0 (1 :100 )
real*4 SUM(1 :100 )
DO 999 , I=1 , 100
SUM(I) = U0(I) + V0(I)
999 CONTINUE
配列の要素を足し合わせてSUMに代入するだけです。繰り返し回数は100回となっています。100とは4の倍数なのでSIMD演算を使うには都合がいいです。これを以下のように書き換えます。
real*4 U0 (1 :100 )
real*4 V0 (1 :100 )
real*4 SUM(1 :100 )
DO 999 , I=1 , 25
SUM(I*4 +0 ) = U0(I*4 +0 ) + V0(I*4 +0 )
SUM(I*4 +1 ) = U0(I*4 +1 ) + V0(I*4 +1 )
SUM(I*4 +2 ) = U0(I*4 +2 ) + V0(I*4 +2 )
SUM(I*4 +3 ) = U0(I*4 +3 ) + V0(I*4 +3 )
999 CONTINUE
と、4回分を展開して全体のループは25回にします。さっきとやっていることは同じなのですが、ベクトルの記述がみられます。コンパイラーはこれを検知してこの部分にSIMD命令を組み込みます。ループが4の倍数である場合、こうしてしまった方が高速になるのです。今度実行速度の比較を載せてみます。
今回は足し算の例だけを見せたのですが、掛け算、割り算、引き算も各要素ごとに一撃でやってくれる命令が積まれています。もっとすごいのが内積を一撃で計算する命令です。IntelのFortranコンパイラは内積命令まで適応してくれますのでぜひとも活用しましょう。
追記 :
コンパイラの設定は必要です。プロジェクトの、プロパティー、Fortran、コード生成、拡張命令セットの有効化でSSEを指定します。SSE4が内積命令まで入っているやつです。たぶん。SSE4はCore 2 Due世代のCPUでは動きませんので注意してください。 なお、Intel Visual Fortranの最新版、もしくはVisual C++の最新版ならAVX命令が使えます。2013年 現在