リストをn個ずつのリストに分割する

[a, b, c, d, e, f, g, h]というリストを3個ずつに分割して、最後の要素は3個以下の[a, b, c], [d, e, f], [g, h]というリストを得たいとする。そのときC#でどう書くかという質問の答えとして人気があるのが、以下のLINQである。

stackoverflowでは二つの質問(Split a List into smaller lists of N size [duplicate]Split List into Sublists with LINQ)の合計で900以上のup voteを得ている。

しかし、この方法は性能の点では最悪である。まずリストの要素数だけIndexとValueを持つオブジェクトを生成している。オブジェクトの生成はメモリと速度両面で、とてもコストの高い操作である。それと比べると小さなコストではあるが、要素の数だけ除算と比較を行うのも性能上よくない。

速度にこだわるのならそもそもLINQを使わなければいいのだが、もしLINQで簡潔に書きたいのなら、以下の解はどうだろうか。

以下はBenchmarkDotNetで取った、1000要素のintの配列を3個ずつに分割するベンチマークの結果である。最初に挙げたものがSplitter1、これがSplitter2である。Meanが平均値で、それ以外は測定誤差を表している。Meanを見るとSplitter2が3.5倍以上速いのがわかる。

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.1766 (21H2)
AMD Ryzen 7 3800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.106
  [Host]  : .NET 6.0.6 (6.0.622.26707), X64 RyuJIT
  LongRun : .NET 6.0.6 (6.0.622.26707), X64 RyuJIT

Job=LongRun  IterationCount=100  LaunchCount=3  
WarmupCount=15  
MethodMeanErrorStdDev
Splitter189.94 μs0.351 μs1.779 μs
Splitter224.04 μs0.100 μs0.517 μs

ただし、.NET 6にはChunkという標準のメソッドが追加されているので、.NET 6以降が対象ならChunkを使うべきだ。Chunkを加えて上と同じベンチマークを実行すると結果はこうなる。Splitter2と比較しても4,000倍以上速い。

MethodMeanErrorStdDev
Splitter188,650.102 ns245.2557 ns1,251.8625 ns
Splitter223,481.503 ns117.8934 ns600.6975 ns
Chunk5.609 ns0.0198 ns0.0984 ns

コメントを残す

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