.net7.0で、各種コレクションの読取パフォーマンスを測ってみた

.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());
}
投稿日:
カテゴリー: C#

コメントする

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