gfortran の出力バッファリング

  • 4
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

処理に時間のかかるプログラムで、途中経過のログをファイルに出力してチェックするなんてことはよくあると思う。でも gfortran で何も考えずにファイルに write するプログラム書くと、出力がバッファされてヘタするとプログラムが終了するまでファイルに書き出さなかったりするんだ。ΩΩΩ<ナ、ナンダッテー

gfortran の出力バッファリングについて

タイトルに gfortran ってつけてるけど他の Fortran コンパイラでも似たようなものじゃないかな? しらんけど。

ログを出力するサンプル

まずはログ出力のサンプルプログラム。1秒毎にカウンタを計20回ファイルに出力するだけ。

test_io_buffering.f90
program test_io_buffering
  implicit none
  integer :: u, i
  open(newunit=u, file='test.log', status='replace')
  do i = 1, 20
     write(u, '(*(g0))') 'i = ', i
     call sleep(1)              ! SLEEP is GNU extension
  end do
  close(u)
  call exit(0)                  ! EXIT_OK
end program test_io_buffering

これを gfortran でコンパイルして実行する。プログラムが終了する前に test.log の内容を確認すると空のファイルになっているのに、プログラム終了後に確認すると 'i = 1' から 'i = 20' までちゃんと出力されている。

いい忘れてたけど open 文の newunit 指定子とか write 文の g0 フォーマット編集記述子は Fortran 2008 で、sleep は gfortran なんでそうでないとこでは適当に読み替えてほしい。

どうしてこうなった

これはプログラムの出力が一旦バッファに溜められてまとめてファイルに書きだされるようになっているから。

バッファに溜める理由は高速化のため。

通常ファイルじゃなくて標準出力 (UNIT=6) や標準エラー出力 (UNIT=0) に出力する場合、端末に出力するときはバッファされないんだけれど、ファイルにリダイレクトするとバッファされる。パイプに対してはバッファされないようだ。

UNIT コマンド バッファの有無
6 ./a.out バッファされない
6 ./a.out > test.log バッファされる
6 ./a.out 2>&1 | cat > test.log バッファされない
0 ./a.out バッファされない
0 ./a.out 2> test.log バッファされる
0 ./a.out 2>&1 | cat > test.log バッファされない

上は UNIX での例だが Windows でもコマンドプロンプトで同じようにして試せる。cat はないがこの例くらいなら代わりに more が使える。ていうか、コマンドプロンプトで標準エラー出力もちゃんとリダイレクトできたんだな。

バッファさせない方法

明示的に flush(unit) を実行する

明示的に flush(unit) を実行するとバッファがフラッシュされる。flush は Fortran 2003 標準。デメリットはめんどくさいこと。

明示的にflushする例
  do i = 1, 20
     write(u, '(*(g0))') 'i = ', i
     flush(u)                   ! write したあと明示的に flush する
     call sleep(1)              ! SLEEP is GNU extension
  end do

環境変数で指定する

環境変数で GFORTRAN_UNBUFFERED_ALL=y を指定するとバッファしないようになる。値は 'Y' でも '1' でも可。ユニットごとには指定できないし write するたびに flush するのでかなり遅くなるかもしれない。

環境変数で指定する例
$ GFORTRAN_UNBUFFERED_ALL=y ./a.out

似たような環境変数に GFORTRAN_UNBUFFERED_PRECONNECTED というのもある。こちらは標準出力と標準エラー出力にのみ作用する。値の指定の仕方は同じ。

変数名からわかるように gfortran 限定。

どうすべきか

ケースバイケースです (おわり)

普通にログ出力として使いたかったらめんどくさくても要所要所に flush を指定するべきじゃないかな。

デバッグ時に使うだけなら環境変数で指定するのもありだと思う。

個人的には open のときに指定したかった (ベンダ拡張でそういうのもあるみたい)。

おわりに

他の Fortran コンパイラだとどうなのか

ifort だとどうなんだろうと思って Intel Fortran でもインストールしてみようと思ったら
無償版は廃止 https://software.intel.com/en-us/forums/topic/533638 だそうな。

じゃあもういいや。