System.IO.Pipelinesに興味をもったので、試しに作ってみました。
題材はFileStreamから読み込んだデータを加工する処理をイメージしています。
(追記:前のバージョンだと出力データまで計測していたので除外しました。これでデータ加工処理のみ計測できたかと)
Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|
UseStreamReaderAsync | 348.453 ms | 6.9207 ms | 12.3016 ms | 1.00 | 44000.0000 | 15000.0000 | 3000.0000 | 267,951,480 B |
Pipe_SeqPos_MemoryAsync | 12.857 ms | 0.0424 ms | 0.0354 ms | 0.04 | 203.1250 | - | - | 1,312,129 B |
Pipe_SeqPos_StructAsync | 8.830 ms | 0.0404 ms | 0.0378 ms | 0.03 | 62.5000 | - | - | 448,129 B |
Pipe_SeqReader_StructAsync | 7.906 ms | 0.1535 ms | 0.2049 ms | 0.02 | 70.3125 | - | - | 446,217 B |
ReadStreamStruct | 5.128 ms | 0.0762 ms | 0.0595 ms | 0.01 | - | - | - | 132 B |
処理概要
1行分のデータを切り出して展開用のクラスに格納します。
クラスのプロパティで項目の値を管理します。
最終的にCSVっぽい形式でデータを出力します。
UseStreamReaderAsyncについて
StreamReaderを使ってクラスに展開→ファイル出力といった一般的なコード。
このお題だと、一度文字列に変換してから、項目ごとに切り出し、再び出力するだけなので効率悪いです。
変換せずにそのまま扱いたいですね。
Pipe_SeqPos_MemoryAsyncについて
Pipeのよくあるサンプルをベースにしたものです。
SequencePositionを使って改行コードを探す方式。
前のものに比べて文字列に変換しないので速いです。
しかし、ここでつまづいたのがReadOnlySequenceの扱い方。
byte配列が欲しいけど、複数のセグメントに分かれた場合どう扱うんだろうと悩みました。
結局.ToArray()使ってコピーする方式になりました。
var line = lineSegment.IsSingleSegment
? lineSegment.First
: lineSegment.ToArray();
Pipe_SeqPos_StructAsyncについて
先ほどの場合とほぼ同じですが
1行分を切り出した後、Span型のまま扱うことで効率よく処理できるかを確認しました。
Allocatedが減って効率良くなりました。
処理時間的には誤差の範囲。
Pipe_SeqReader_StructAsyncについて
タイトルにも書きましたが、SequenceReaderというものがあります。
こちらを介して処理すると、いい感じに1行分のReadOnlySpanに切り出してくれます。
コードの方も見通しがよくていい感じです。
var sequenceReader = new SequenceReader<byte>(buffer);
while (!sequenceReader.End)
{
while (sequenceReader.TryReadTo(out ReadOnlySpan<byte> line, _crlf))
OutputReadOnlySpan(output, line);
buffer = buffer.Slice(sequenceReader.Position);
sequenceReader.Advance(buffer.Length);
}
後続の処理がSpanのまま扱えるならこの方が便利ですね。
Memory型で扱いたい場合はPipe_SeqPos_MemoryAsyncのパターンの方が便利かも。
ReadStreamStructについて
最後に比較対象としてPipeを使わないバージョン
固定長を前提にするならPipe使わない方がシンプルに書けますね。
Allocatedも最小なのでスピード狂にも安心です。
しかし、データ長が不明の場合には、
自前で改行コード探すことになるので大変そうです。
その場合は、Pipe使うと楽ができそうです。
あと、IBufferWriterを使ってみたかったのですが、使い方がよくわらかないので断念しました。今後の課題ということで。