Gauche:BufferedIO

Gauche:BufferedIO

Gauche-0.5.4に入る(予定)バッファードI/Oルーチンについてのメモ。

0.5.3までの問題

FILE* 構造体

0.5.3までは、通常のファイルI/OはFILE* を通して行っていた。 これは簡単なんだが、いろいろ不便なこともある。

それに、charconv絡みで既に自前でバッファリングを行うポートも 実装されているんだし、それなら最初からバッファードI/Oを自分で やることにしてもコードサイズは増えないだろう。

マクロの濫用

0.5.3までのSCM_GETC等は既に巨大なマクロになっていて、ちょっと気になっていた。 果してこれらをマクロ展開することは性能に寄与してるんだろうか。 関数呼び出しにしても、そいつがIキャッシュに乗っかってしまえば さしてペナルティは無く、むしろ全体のコードサイズが小さくなるぶん 有利なんじゃないか。


というわけでFILE*を使わずに自前でのバッファードI/Oに切り替えてみる。 port.cを大幅に書き換え。基本的なところは一日で動くようになったが、 いくつか落し穴が。

exit時のバッファのフラッシュ

exit(3)はシステムのFILE* をフラッシュしてくれるが、ユーザが実装した バッファは知ったこっちゃないので、Scm_Exitの中からフラッシュするようにする。

ところが、だ。フラッシュするためには現在オープンされている出力ポートを 把握しておかなければならない。ポートがオープンされた時点で内部のリストに つないで、クローズされた時点ではずすようにすると、作りっぱなしで参照 されなくなったポートがGCされなくなってしまう。

いくつかfinalizationを使ったトリックを考えてみたが、 結局素直にweak pointerを実装するのが一番よさそうだ。 Boehm GCにはweak pointerを実装するAPIがある。

Schemeレベルにweak pointerを見せる方法としては、

などがあるが、Boehm GC的にはweak vectorが一番素直そうなのでそうする。 (weak pointerはATOMICでアロケートしなければならないので、 weak boxやweak pairだと1オブジェクトあたり2回アロケーションが 必要。weak vectorならベクタサイズ分のオブジェクト全体に対して2回で良い)。 weak hash tableは便利そうだが、必要ならweak vectorの上に作れるだろう。

というわけでport.c内部でオープンされた出力ポートへのweak pointerを 管理することでExit時のフラッシュの問題は解決。

バッファリングモード

出力に関してはとりあえずstdioと同じように「出来る限りバッファ」 「ラインバッファ」「バッファ無し」を用意。 但し、「出力先と同じターミナルから入力が要求された時にはフラッシュ」 はやらない。明示的にflushすればいいんじゃなかろうか。

入力では「ラインバッファ」の意味はあまりない。その時点でavailableな データをすべてバッファに取り込むか、要求されたサイズだけの取り込むか という違いがあるだけだ。但し、要求されるデータのサイズによっては サブシステムが2回以上read(2)を呼ばねばならない場合がある (バッファの 容量より大きなデータが要求された場合など)。その場合、 たとえばcallerがあらかじめselect(2)でデータがあることをチェックしていた としても、2回目のread(2)でブロックする可能性がある。アプリケーションによっては それは避けたいだろう。ということで、以下の3モードを用意する。

他に必要なモードってあるかなあ。

例外コンディションの管理

feof(3)とかferror(3)とかに相当するAPI。まだつけてない。どうしようかなあ。


結果

ナイーブな実装でも、0.5.3より良い性能が出てる。 2002/04/24 21:41:08 PDT時点で、単純なキャラクタI/Oの繰り返しで10%くらいの改善。 read-line等はこれからいじる。

コードサイズが10%くらい小さくなった。やっぱりGETCマクロはでかすぎたようだ。


Last modified : 2012/02/07 08:04:45 UTC