結論としては以下の通り。
- 配列は for と foreach の速度はほぼ同じ
- List<T> は for のほうが foreach より10%以上早い
- 配列に対する操作は List
に対する操作より 50%以上早い - List<T> クラスの ForEach メソッドはメリットが無いので使わないほうがいい
また、決まった長さで処理ができる場合配列を使用したほうが常に高速です。
では説明です。
確認環境
- .NET6 + C# 10.0
- VisualStudio 2022
- Windows 11
- AMD Ryzen 9 5900X
- 計測には BenchmarkDotNetを使用
- Relaseビルドしたバイナリをコンソールから実行して確認
補足:
ランタイムのバージョンや言語バージョン(コンパイラのバージョン)PC環境によって変動します。あくまで記載の環境で実行したらこうなります。
計測結果サマリー
| Method | Mean | Error | StdDev | |-------------------- |-----------:|---------:|---------:| | ArrayForTest | 572.5 ns | 5.39 ns | 4.21 ns | (1) 配列をforで回す | ArrayForEachTest | 575.2 ns | 6.33 ns | 5.92 ns | (2) 配列をforeachで回す | CreateTestDataArray | 313.3 ns | 3.02 ns | 2.83 ns | (1)と(2)のテストデータ作成 | ListForTest | 1,295.9 ns | 12.07 ns | 10.70 ns | (3) List<T>をforで回す | ListForEachTest | 1,482.5 ns | 23.70 ns | 22.17 ns | (4) List<T>をforで回す | ListForEachMethodTest | 2,557.3 ns | 50.65 ns | 49.74 ns | (5) List<T>のForEachメソッドで回す | CreateTestDataList | 791.4 ns | 14.29 ns | 13.37 ns | (3)と(4)のテストデータ作成
データ生成を除外すると以下の通りです。
項目 | 速度 |
---|---|
(1) 配列 + for | 259.2ns |
(2) 配列 + foreach | 261.9ns |
(3) List + for | 504.5ns |
(4) List + foreach | 691.1ns |
(5) List + ForEeachメソッド | 1766.3ns |
List<T>.ForEach メソッド*1を使用したループだけ突出して遅いです。このメソッドを使ってスマートに書ける訳でもなく単に処理速度が遅いだけなので使用しないほうがよいでしょう。
計測コード
確認コードは以下の通りです。
単純に1000件の配列とリストを作成してそれに対してシーケンシャルアクセスを実行しています。
using System.Collections.Generic; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; namespace Takap.Performance { public class AppMain { public static void Main(string[] args) { BenchmarkRunner.Run<Test>(); } } public class Test { private const int cnt = 1000; private int _sum1; private int _sum2; private int _sum3; private int _sum4; // (1) 配列 + for [Benchmark] public void ArrayForTest() { var array = CreateTestDataArray(); for (int i = 0; i < array.Length; i++) { _sum1 += array[i]; } } // (2) 配列 + foreach [Benchmark] public void ArrayForEachTest() { var array = CreateTestDataArray(); foreach (int value in array) { _sum2 += value; } } [Benchmark] public int[] CreateTestDataArray() { int[] array = new int[cnt]; for (int i = 0; i < array.Length; i++) { array[i] = i; } return array; } // (3) List + for [Benchmark] public void ListForTest() { var list = CreateTestDataList(); for (int i = 0; i < list.Count; i++) { _sum3 += list[i]; } } // (4) List + foreach [Benchmark] public void ListForEachTest() { var list = CreateTestDataList(); foreach (int value in list) { _sum4 += value; } } // (5) List + ForEach() [Benchmark] public void ListForEachMethodTest() { var list = CreateTestDataList(); list.ForEach(value => _sum4 += value); // ★ListクラスのForEachメソッドは遅いので使わないほうがいい } [Benchmark] public List<int> CreateTestDataList() { List<int> array = new(cnt); for (int i = 0; i < cnt; i++) { array.Add(i); } return array; } } }
計測用のライブラリで速度を計測しているのでPC環境に左右されない + 簡単に計測できるでとても楽ですね。
最後に
ちなみに、.NET 6 のほうが最適化が進んているのか .NET Core 3.1 よりかなり速度が高速化しています。
で、ここまで長々と書いておいてなんですが、この後、実際はループの中に書く処理の方が何倍も重たいでしょうし、どう書いたところでループの処理コストなんて全体の中で本当に極小のため、こんな事を気にして実装すること自体がナンセンスかもしれませんが目安程度に参考にしてください。
まぁ、この結果に拘泥せず状況によって使い分けたほうがいいのかもしれませんね。
*1:List<T> クラスの ForEach メソッドはよく Linq メソッドと言われますが List クラス固有のメソッドで Linq とは何の関係もありません。