ぼやきごと/2020-01-18/C#:続! ReadOnlySpan と Range を駆使して高速文字列分割 の変更点


#blog2navi()
*C#:続! ReadOnlySpan と Range を駆使して高速文字列分割 [#ya5d687d]
LEFT:Prev: [[[C#: ReadOnlySpan と Range を駆使して高速文字列分割>ぼやきごと/2020-01-10/C#: ReadOnlySpan と Range を駆使して高速文字列分割]]]

前回、 @code{ReadOnlySpan<T>}; と @code{Range}; を駆使して高速に文字列分割((というか文字列に限らず @code{ReadOnlySpan<T>}; なら何でも分割))する拡張メソッドを作り、そのベンチマークを行いました。

…が、「ベンチマークに [[BenchmarkDotNet>https://github.com/dotnet/BenchmarkDotNet]] を使わないとか舐めてるの?C#erの面汚しめ!」という心の声に悩まされるようになったので全面的に書き換えました。

[[GitHub: ruche7/Test_SplitReadOnlySpan>https://github.com/ruche7/Test_SplitReadOnlySpan]] (以前のコードは [[boyaki_200110>https://github.com/ruche7/Test_SplitReadOnlySpan/tree/boyaki_200110]] タグにあります)
[[GitHub: ruche7/Test_SplitReadOnlySpan at boyaki_200118>https://github.com/ruche7/Test_SplitReadOnlySpan/tree/boyaki_200118]] (以前のコードは [[boyaki_200110>https://github.com/ruche7/Test_SplitReadOnlySpan/tree/boyaki_200110]] タグにあります)

-[[BenchmarkDotNet>https://github.com/dotnet/BenchmarkDotNet]] を使うようにした。
-余計な処理はせず、 @code{string}; を改行で区切って @code{string[]}; にするまでの処理に絞って計測するようにした。
--セパレータは @code{'\n'}; オンリーと @code{"\r\n", "\n", "\r"}; 混合の2パターン。
-@code{SpanExtensions}; 静的クラス(@code{ReadOnlySpanExtensions}; から名前変更)に拡張メソッドを追加。
--@code{Span<T>}; に対応。
--@code{ReadOnlySpan<char>}; と @code{Span<char>}; 限定で複数セパレータ(@code{string[]};)による分割に対応。(@code{string.Split}; では元々可能)

事前に下記の静的フィールドを準備しておきます。

#code(csharp,nomenu,nonumber,nooutline,noliteral,nocomment){{
// 改行を表す文字列の配列
private static readonly string[] LineBreaks = { "\r\n", "\n", "\r" };

// LineN = N行の文字列
// 改行コードは "\r\n", "\n", "\r" 混在
private static readonly string Line1 = MakeString(1);
private static readonly string Line10 = MakeString(10);
private static readonly string Line100 = MakeString(100);
private static readonly string Line1000 = MakeString(1000);
private static readonly string Line10000 = MakeString(10000);
}}

計測対象メソッドは下記の4パターン。 '''N''' は上記フィールドの通り 1, 10, 100, 1000, 10000 の5通りで、総メソッド数は20個。

:@code{String_SplitNewLine_Line'''N'''};|
--文字列 @code{Line'''N'''}; を @code{string.Split('\n')}; で分割する。
:@code{Span_SplitNewLine_Line'''N'''};|
++文字列 @code{Line'''N'''}; を @code{string.AsSpan()}; で @code{ReadOnlySpan<char>}; に変換する。
++@code{ReadOnlySpan<char>.SplitToRanges('\n')}; で各分割文字列範囲を表す @code{List<Range>}; を得る。
++@code{new string[ranges.Count]}; で作成した文字列配列に各行を @code{span[ranges[i]].ToString()}; で設定。
:@code{String_SplitLineBreaks_Line'''N'''};|
--文字列 @code{Line'''N'''}; を @code{string.Split(LineBreaks, StringSplitOptions.None)}; で分割する。
:@code{Span_SplitLineBreaks_Line'''N'''};|
++文字列 @code{Line'''N'''}; を @code{string.AsSpan()}; で @code{ReadOnlySpan<char>}; に変換する。
++@code{ReadOnlySpan<char>.SplitToRanges(LineBreaks)}; で各分割文字列範囲を表す @code{List<Range>}; を得る。
++@code{new string[ranges.Count]}; で作成した文字列配列に各行を @code{span[ranges[i]].ToString()}; で設定。

要するに、 @code{'''Xxx'''_SplitNewLine_Line'''N'''}; が単一セパレータ(@code{char};)による分割、
@code{'''Xxx'''_SplitLineBreaks_Line'''N'''}; が複数セパレータ(@code{string[]};)による分割です。

ベンチマークの結果は [[README.md に載せてあります>https://github.com/ruche7/Test_SplitReadOnlySpan#benchmark-results-on-ruche7s-pc]]。 ''Mean'' の列を見ればOK。

1行を単一セパレータで分割するケースを除き @code{ReadOnlySpan<char>.SplitToRanges}; を使う方が高速で、特に複数セパレータの場合は2〜5倍もの速度差になっています。

自作のツール等では積極的に利用していきたいですね。

RIGHT:Category: &#x5b;[[C#>ぼやきごと/カテゴリ/C#]]&#x5d;&#x5b;[[Visual Studio>ぼやきごと/カテゴリ/Visual Studio]]&#x5d;&#x5b;[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]&#x5d; - 2020-01-18 06:03:49
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()