Streamのデータを全てbyte[]に読み込む方法

HttpClient,HttpListenerに関係して、Streamのデータを全てbyte[]に読み込む方法を探してみると、
「StreamReader でファイルをすべて読み込む」とかが出てきてそれを参考にコードを書いてみる。

public static byte[] Source { get; set; } = Enumerable.Range(32, 80).Select(x => (byte) x).ToArray();

[Benchmark]
public byte[] StringStreamCopy()
{
    var mem1 = new MemoryStream(Source);
    System.IO.StreamReader reader = new System.IO.StreamReader(mem1, Encoding.UTF8);
    string content = reader.ReadToEnd();

    var result = Encoding.UTF8.GetBytes(content);
    return result;
}

おー、きちんとコピーされている。
よかった。よかった。おしまい。

じゃなくて、動いたのはいいとして、これ、余計な仕事しまくっているよね。
今回のケース、元のstreamのデータはutf8です。
それをstring(=utf16)に読み込むから、変換が。
それを、byteに戻すのに、また変換が。

この無駄は省きたいので、もっと調べていくと、どうもMemoryStreamを使うとよいらしい。
それを含めて、いろいろ書いてみてベンチマークを取ってみました。

public class BenchTarget
{
    public static byte[] Source { get; set; } = Enumerable.Range(32, 80).Select(x => (byte) x).ToArray();
    public static byte[] Buffer { get; set; } = new byte[8192];

    public static MemoryStream ShareMemoryStream { get; set; } = new MemoryStream(Buffer);

    [Benchmark]
    public byte[] ToArray()
    {
        return Source.ToArray();
    }

    [Benchmark]
    public byte[] MemoryStreamCopyUseBuffer()
    {
        var mem1 = new MemoryStream(Source);
        var mem2 = new MemoryStream(Buffer);
        mem1.CopyTo(mem2);

        var result = Buffer.AsSpan(0, (int)mem2.Position).ToArray();

        return result;
    }

    [Benchmark]
    public byte[] MemoryStreamCopyUseShare()
    {
        var mem1 = new MemoryStream(Source);
        var mem2 = ShareMemoryStream;
        mem2.Position = 0;
        mem1.CopyTo(mem2);

        var result = Buffer.AsSpan(0, (int)mem2.Position).ToArray();

        return result;
    }

    [Benchmark]
    public byte[] MemoryStreamCopyNoBuffer()
    {
        var mem1 = new MemoryStream(Source);
        var mem2 = new MemoryStream();
        mem1.CopyTo(mem2);

        var result = mem2.ToArray();

        return result;
    }

    [Benchmark]
    public byte[] StringStreamCopy()
    {
        var mem1 = new MemoryStream(Source);
        System.IO.StreamReader reader = new System.IO.StreamReader(mem1, Encoding.UTF8);
        string content = reader.ReadToEnd();

        var result = Encoding.UTF8.GetBytes(content);
        return result;
    }
}

//|                    Method |      Mean |    Error |   StdDev |    Median |
//|-------------------------- |----------:|---------:|---------:|----------:|
//|                   ToArray |  30.79 ns | 0.418 ns | 0.370 ns |  30.75 ns |
//| MemoryStreamCopyUseBuffer |  47.78 ns | 0.897 ns | 1.524 ns |  46.99 ns |
//|  MemoryStreamCopyUseShare |  43.58 ns | 0.671 ns | 0.560 ns |  43.41 ns |
//|  MemoryStreamCopyNoBuffer |  66.72 ns | 1.390 ns | 1.365 ns |  66.39 ns |
//|          StringStreamCopy | 338.48 ns | 6.512 ns | 5.084 ns | 337.17 ns |

やっぱり、StringStreamCopyが遅いですね。5~10倍くらい。
ToArrayは単純コピーなのでやはり速い。
Streamで書くとMemoryStreamを使うと、多少のオーバーヘッドで簡単に書けます。
繰り返し処理の場合、Bufferを使いまわすと処理が速くなりそうです。
ただ、今回の様な書き方は、スレッドセーフじゃないのでマルチスレッドで使う場合は、Lockが必要です。

投稿日:
カテゴリー: C#

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です