薄いブログ

技術の雑多なことを書く場所

Go 1.15 から io.CopyBuffer はコピー先が *os.File だと指定したバッファーを使わない

TL;DR

Go 1.15 から io.CopyBuffer はコピー先が *os.File だと指定したバッファーを使わない

6/3 21:57 追記

既知の問題でリリースノートにも書いてありました、情報提供ありがとうございます!

Go 1.15 Release Notes - The Go Programming Language

本題

Go で io.Reader から io.Writer に対してデータをコピーしたいとき io.Copy を使うと思います。

io.Copy は io.WriterTo や io.ReaderFrom が実装されている対象に対してはそれを使います。 そうでない場合は 32 キロバイトのバッファーを使ってコピーを行います。

この 32 キロバイトのバッファーが毎回メモリ確保されるのでループなどで io.Copy を使う場合は予めバッファーを確保しておいて io.CopyBuffer を使うかなと思います。

ですが Go 1.15 からコピー先が *os.File だと io.CopyBuffer が渡したバッファーを使ってくれません。

github.com

*os.File は io.ReaderFrom が実装されているのでその分岐に入ります。

*os.File の ReadFrom の実装は以下です。

github.com

プラットフォームごとに異なる実装の readFrom に処理が移譲されています。現時点だと実装されているのは linux だけでそれ以外は stub の実装です。

github.com

linux の実装も基本的には readFrom の先が *os.File だったときに copy_file_range(2) を使うためのものです。

コピー元が *os.File じゃなければ genericReadFrom にフォールバックして io.Copy が使われてしまいます。(無限ループにならないために Write しか実装してない onlyWriter という型で包んでいる)

ReadFrom にはバッファーを受け取る口がないので渡したバッファーを考慮できません。

結論

io.CopyBuffer はコピー先が *os.File だと指定したバッファーを使いません。

バッファーを指定したい場合は Write のみを実装した onlyWriter で包んで io.CopyBuffer にわたす必要があります。