2015-01-18

Fortran でのバイナリ (unformatted, binary) の扱いについてのメモ

前提

FIELDVIEW で Plot3D の binary 読み込む というのを昨日書いた。だけど、Plot3D だけじゃなくて、ファイルサイズ削減のため Fortran でバイナリ形式で読み書きしたい。ただそもそも unformatted まわりがよくわからん…。たとえば form="unformatted" と form="binary" はどう違うのか?あまり変わらないという話もある(参考1, 参考2)けど、じゃあ多少は違うのだろうか?とか。

そこで、ググって調べつつテストコード書いてバイナリエディタで中身開いて確認してみた。結果、ちょっとずつわかってきた。今のところの理解をメモしておく。

なお character についてはとりあえず考えていない。 integer, real(4), real(8) のみ。バイトオーダーも気にしてない。

比較

まずバイナリ形式を扱うための open にはいくつかの形式がある。恐らく次の4種類?
  • open( ..., form="unformatted", access="sequential")
  • open( ..., form="unformatted", access="direct", recl=数字) ← position 指定不可。 ifort での場合コンパイル時は "-assume byterecl" を付けるべし
  • open( ..., form="unformatted", access="stream") ← Fortran 2003 以降(gfortran, g95, ifort 全部大丈夫そう。よほど古くなければ)
  • open( ..., form="binary") ← ifort 独自仕様で stream と同義
一番基本的なのが sequential のようだが、これだと一行というか ( a(i), i=1,10 ) みたいな塊の前後にヘッダとフッタが自動的に挿入される。したがって縦に長いというか do i=1,1000000 みたいに改行しまくるとヘッダとフッタ分でサイズが増える。

direct はそういうことはないが、読み書き位置を read(m_num, rec=i) みたいに一々指定しないといけないようで、小さいデータならいいが複雑になるとミスりそうでちょっと怖い。あと
positon="append" を付けるとコンパイルの時点で怒られる。どうやら、 position="append" は direct や stream だとできなそう。それから、integer, single precision, double precision が混在するときは、一番大きな double precision に合わせて record という固定長の領域(?よくわかってないがそんなイメージ)を確保しないといけないっぽい。そうすると integer, single precision の部分はデータは半分だけで残りは0でフィルされてだいぶ無駄になってるみたいだ。

stream は新しいやり方…というか C/C++ に合わせたい( の stream?)とかのモチベーションで新しく出てきたやつらしい。固定長の record base じゃないので変数の型に合わせて必要なサイズずつ入っていく。型が混在する場合はファイルサイズの節約になる。また、読むときは read(m_num, posipos=i) みたいに指定するようで、direct ほどでなくとも頭は使いそうだが、書き出しの際は rec= なんてのはないので、direct よりも楽。なお http://www.star.le.ac.uk/~cgp/streamIO.html によると formatted でも使えるとのこと。

結論

というわけで、なるべく横長にして sequential を使うか、stream かのいずれかにしようと思う。

参考リンク

一部は本文中と重複してる。

テストに使ったコード

https://docs.google.com/document/d/1m9iOSI26PJcyJEZuVSuMiwTc0miMOMwMwp5prEAqFjc/edit?usp=sharing
(google docs が開くのでコピペして保存したら使えると思う)


UPDATE 2015-01-19

実際に actionaccess="sequential" にして、横に長く write(iunit) ( a(i), i=1,4800000 ) みたいにしたやつと do i=1,4800000; write(iunit) a(i); enddo にしたやつを比較すると、前者は後者の 1/3 くらいのサイズになった。

また、テキスト (form="formatted") 形式についても今まであまり気にしていなかったがこの違いはあり、やはり横に長くした方が 1/3 くらいになる。

一例として、4800000 個の単精度データを吐き出してみた。テキストの場合、フォーマットは 1x,es13.6 にしてみた。単にバイナリとあるのは form="unformatted", actionaccess="sequential" のこと。

  • テキスト(縦長): 68,688 KB
  • テキスト(横長): 24,403 KB
  • バイナリ(縦長): 56,250 KB
  • バイナリ(横長): 18,751 KB
  • バイナリ (actionaccess="stream"): 18750 KB
まずわかるのは actionaccess="stream" (ifort でいう form="binary")は横長の action="sequential" とほとんど変わらない、ということ。

それに、横長テキストもかなり小さいので、こっちでも別にいいかなと思える。…が、横長テキストってフォーマット指定時に繰り返し回数のところに変数が使えないもんだから、横に並ぶデータの個数をハードコーディングしないとダメなんだよね(…たぶん。違ってたら教えて…)。どういうことかというと…たとえばデータの個数が imax = 4800000 だったとして、

  write(iunit, "(imax(1x,es13.6))") ( a(i), i=1,imax)

とはできないので、

  write(iunit, "(4800000(1x,es13.6))") ( a(i), i=1,imax)

とするしかない(ようだ)、ということ。

再度結論

したがって、再度結論をまとめると、
  • 中身を確認したいような場合: 縦長テキスト形式(横長だと重すぎて開けなくて意味ない)
  • 後で流れ場の処理とかしたいから出しとく、みたいなの: 横長のバイナリ (actionaccess="sequential")
  • FV用の Plot3D: バイナリ (actionaccess="stream")
としようと思う。


(最終更新 16:51)
(2015-10-10 typoを修正: action→access)

No comments: