.net7.0がリリースされたので、各種コレクションの読取パフォーマンスを測ってみた
計測するのは、forかforeachで、全要素を足すだけ。
100個/10000個、.net6.0/.net7.0のケースを計測
| | Count:100 | Count:100 |Count:10000 |Count:10000 |
| Method | .net7.0 | .net6.0 | .net7.0 | .net6.0 |
|------------------------ |------------:|------------:|-----------:|-----------:|
| ArrayForBench | 42.47 ns | 38.58 ns | 3.528 us | 3.228 us |
| ArrayForEachBench | 42.01 ns | 38.94 ns | 3.569 us | 3.865 us |
| ArrayAsSpanForEachBench | 39.18 ns | 38.39 ns | 3.528 us | 3.364 us |
| ListForBench | 56.36 ns | 57.58 ns | 5.083 us | 5.593 us |
| ListForEachBench | 80.91 ns | 106.35 ns | 7.553 us | 9.870 us |
| ListAsSpanForEachBench | 40.09 ns | 39.52 ns | 3.499 us | 3.368 us |
| LinkedListForEachBench | 367.83 ns | 367.11 ns | 34.507 us | 35.119 us |
| SortedSetForEachBench | 1,250.84 ns | 1,324.22 ns | 126.637 us | 137.062 us |
| HashSetForEachBench | 262.75 ns | 240.35 ns | 25.281 us | 22.881 us |
| StackForEachBench | 290.91 ns | 265.90 ns | 27.254 us | 24.803 us |
| QueueForEachBench | 291.04 ns | 293.41 ns | 29.902 us | 28.055 us |
説明と考察
まず最速なのは、配列かListをSpan化したもの。
Spanが速い理由は割愛。
Listは、以下でSpan化できるみたいです。
System.Runtime.InteropServices.CollectionsMarshal.AsSpan(list);
続いて、配列が速い。
配列の場合、foreachは、糖衣構文としてforに展開されるらしい。
(https://sharplab.io/ にて調査)
なのでforとforeachはほぼ同じ速さに。
Listの場合、foreachは、以下の様な形に展開される。
List<int>.Enumerator enumerator = list.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
int current = enumerator.Current;
// 処理
}
}
finally
{
((IDisposable)enumerator).Dispose();
}
これが、forより遅くなる。
LinkedList以下は、やはり単純な配列・Listに比べると遅くなる。
特に、並び替えされるSortedSetは、遅くなる。
(内部で赤黒木とか作っているし)
SortedSetは、場合によってすごく便利なんですけどね。
.net6.0/.net7.0の比較としては、単純なケースの為か、速くなったり、遅くなったりと一長一短。
遅くなるケースがあるのは、少し気になったが。
以下、ベンチマークのプログラム
const int Size = 100;
static IEnumerable<int> _source = Enumerable.Range(0, Size);
static int[] _array = _source.ToArray();
[Benchmark]
public int ArrayForBench()
{
int sum = 0;
var target = _array;
for (int i = 0; i < target.Length; i++) { sum += target[i]; }
return sum;
}
[Benchmark]
public int ArrayForEachBench()
{
int sum = 0;
var target = _array;
foreach (var item in target) { sum += item; }
return sum;
}
[Benchmark]
public int ArrayAsSpanForEachBench()
{
int sum = 0;
var target = _array;
foreach (var item in target.AsSpan()) { sum += item; }
return sum;
}
static List<int> _list = _source.ToList();
[Benchmark]
public int ListForBench()
{
int sum = 0;
var target = _list;
for (int i = 0; i < target.Count; i++) { sum += target[i]; }
return sum;
}
[Benchmark]
public int ListForEachBench()
{
int sum = 0;
var target = _list;
foreach (var item in target) { sum += item; }
return sum;
}
[Benchmark]
public int ListAsSpanForEachBench()
{
int sum = 0;
var target = _list;
foreach (var item in System.Runtime.InteropServices.CollectionsMarshal.AsSpan(target)) { sum += item; }
return sum;
}
static LinkedList<int> _linkedList = new LinkedList<int>(_source);
[Benchmark]
public int LinkedListForEachBench()
{
int sum = 0;
var target = _linkedList;
foreach (var item in target) { sum += item; }
return sum;
}
static SortedSet<int> _sortedSet = new SortedSet<int>(_source);
[Benchmark]
public int SortedSetForEachBench()
{
int sum = 0;
var target = _sortedSet;
foreach (var item in target) { sum += item; }
return sum;
}
static HashSet<int> _hashSet = new HashSet<int>(_source);
[Benchmark]
public int HashSetForEachBench()
{
int sum = 0;
var target = _hashSet;
foreach (var item in target) { sum += item; }
return sum;
}
static Stack<int> _stack = new Stack<int>(_source);
[Benchmark]
public int StackForEachBench()
{
int sum = 0;
var target = _stack;
foreach (var item in target) { sum += item; }
return sum;
}
static Queue<int> _queue = new Queue<int>(_source);
[Benchmark]
public int QueueForEachBench()
{
int sum = 0;
var target = _queue;
foreach (var item in target) { sum += item; }
return sum;
}
[TestMethod]
public void SumTest()
{
var instance = new BenchTarget();
var a = instance.ArrayForBench();
Assert.IsTrue(a == instance.ArrayForEachBench());
Assert.IsTrue(a == instance.ListForBench());
Assert.IsTrue(a == instance.ListForEachBench());
Assert.IsTrue(a == instance.ListAsSpanForEachBench());
Assert.IsTrue(a == instance.LinkedListForEachBench());
Assert.IsTrue(a == instance.SortedSetForEachBench());
Assert.IsTrue(a == instance.HashSetForEachBench());
Assert.IsTrue(a == instance.StackForEachBench());
Assert.IsTrue(a == instance.QueueForEachBench());
}