C# List<T>のforeachループの最速を検証してみる
元ネタはこちらのブログです。 (C#) List<T>からSpan<T>を引き抜いて高速化 - ネコのために鐘は鳴る
知らなかったのですがListのforeachはパフォーマンスが悪いみたいですね。 今回はブログに書いてある手法のパフォーマンスを実測して検証してみようと思います。
今回のテストでは 0~5000の値をリストに入れて、それらの合計を求めるプログラムでパフォーマンスを検証してみます。
検証環境 今回もBenchmarkDotnet / Release , デバッガアタッチ無しです。
リストの定義
private List<int> _list; [GlobalSetup] public void Initialize() { _list = new List<int>(Enumerable.Range(0,5000)); }
まずは普通にforeachを使って回すパターン。一番ありがちなやつですね。
[Benchmark] public int List() { int result = 0; foreach (var item in _list) { result += item; } return result; }
結果は11.506 usでした。
次にAsSpanです。
参照元のブログに書かれていますが、Unsafeを使ってTを強引に取り出すという方法です。
注意しないといけないのはIList
以下、List
public static class ListEx { private class list_internal<T> { internal T[] items; } public static Span<T> AsSpan<T>(this List<T> list) { return Unsafe.As<list_internal<T>>(list).items.AsSpan(0, list.Count); } }
そして同じようにループを回します。
[Benchmark] public int Span() { int result = 0; foreach (var item in _list.AsSpan()) { result += item; } return result; }
結果は...2.529 us !!なんと5倍程度早くなっていることが分かりました。
これはすごいですね。ただ、Unsafe.Asを使うのは今後とも100%安全とは保障できないので必要に応じて自己責任で使うという感じでしょうか。
おまけで、ToArray()での検証もしてみました。
List
int result = 0; foreach (var item in _list.ToArray()) { result += item; } return result;
計測するまでは変換コストが高いからそのまま回すほうが早いだろうと思っていたのですが...
結果はなんと...5.023 us 要素数が5000程度であればList
これは予想外だったのでびっくりしました。ToArray()であれば、普通に使えそうですね。
今回の計測結果をまとめます。