0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[C#] System.IO.PipelinesでReadOnlySequenceの扱いに悩んだのでSequenceReader使ってみた。

Last updated at Posted at 2022-07-18

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を使ってみたかったのですが、使い方がよくわらかないので断念しました。今後の課題ということで。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?