社内の勉強会でGoのメモリアロケーションの話があって、最後に『チャレンジ問題』があった。
こんなの。
チャレンジ問題 ファイルの内容すべてをバイト列に読み込む関数
ReadFile
を(*File).Read
などを用いて実装してください ヒント: リアロケーションが起きないようにするにはどうすればいいだろうか
そういえばC#書いててもファイル読み込みとかStreamReaderとか使ってて低レベルな読み込みしないなぁ、どんな感じになのかなぁ、と思ったのでちょっと見てみた。
結果、.NETでIOに使う Stream
には Length
っていう身もふたもないプロパティがあったのでその分だけメモリを確保すればよかった。
面白みはなかった。
尚 Length プロパティはSeekできないStreamだと例外を吐く模様。ローカルファイルの読み込みならSeekできるだけで、ネットワーク越しのアクセスだと多分使えない。IOは奥が深い。
using System; using System.IO; using System.Text; namespace dotnet_sandbox { // ファイルの内容すべてをバイト列に読み込む関数 `ReadFile` を `(*File).Read` などを用いて実装してください // ヒント: リアロケーションが起きないようにするにはどうすればいいだろうか // 答え合わせは `os.ReadFile` を確認してください // Go 1.15 (ioutil) と1.16の実装の違いを確認し、ベンチマークしてみましょう。 // PrometheusはGo 1.16でテストが落ちるようになりました ([https://github.com/prometheus/prometheus/issues/8403 #8403])。理由を調べてみましょう。 class Program { static byte[] ReadToEnd(Stream stream) { var buf = new Byte[stream.Length]; var i = 0; int b; // ReadByte はStreamが終端だと -1 を返すという現代とは思えないレガシーな仕様 while ((b = stream.ReadByte()) >= 0) { // アンセーフにしてポインタを進めていったほうが早いのか? 分からん // そもそも長さが分かっているなら Read メソッドに buf を渡せば一気に読めそう buf[i++] = (byte)b; } return buf; } static void Main(string[] args) { using (var stream = new FileStream(args[0], FileMode.Open)) { var bytes = ReadToEnd(stream); Console.WriteLine($"{args[0]} length = {bytes.Length}"); Console.WriteLine(Encoding.UTF8.GetString(bytes)); } } } }
*1:こういう時は大体眺めない