Sunday, 30 June 2024

Iterating a list using Span in C# - Benchmark

It is possible to iterate a list in C# using Span. This article shows a benchmark on that and how you can create a Span of a list. It is important to note that the Span will despite it is not doing any memory to memory copying and is performance optimized, it will not
detect changes done to the list after the span is created, therefore the iterations you perform should not be done on a list where you expect that the list is altered during the iteration using the Span. Let's first look at the Benchmark, BenchmarkDotNet will be used. The Github repo with the source code for the benchmark is here: https://github.com/toreaurstadboss/BenchmarkIterateCollections The program is started up using BenchmarkRunner
Program.cs


using BenchmarkDotNet.Running;

namespace BenmarkIterateCollections;


public class Program
{

    public static void Main(string[] args)
    {
        BenchmarkRunner.Run<IterateCollectionsBenchmark>();
        Console.WriteLine("Benchmark finished. Hit the any key to exit");
        Console.ReadKey();
    }
}



The benchmarks look like this, the base line is iterating an array.
BenchmarkIterateCollections.cs


using BenchmarkDotNet.Attributes;
using System.Runtime.InteropServices;

namespace BenmarkIterateCollections;

public class IterateCollectionsBenchmark
{

    private Random _rnd = new Random();
    private int[] _array = default!;

    private List<int> _list = default!;
    private const int ITEM_COUNT = 1001;

    [GlobalSetup]
    public void Init()
    {
        _array = Enumerable.Range(0, ITEM_COUNT).Select(_ => _rnd.Next(0, 1024)).ToArray();
        _list = _array.ToList();
    }

    [Benchmark(Description = "Iterate an array with foreach", Baseline = true)]
    public void IterateArray()
    {
        foreach (var item in _array)
        {
        }
    }

    [Benchmark(Description = "Iterate a list using a span with foreach")]
    public void IterateArrayUsingSpan()
    {
        Span<int> span = _array; //note : implicit operator used here to convert an array to a Span
        foreach (var item in span)
        {

        }
    }

    [Benchmark(Description = "Iterate a list with foreach")]
    public void IterateList()
    {
        foreach (var item in _list)
        {

        }
    }

    [Benchmark(Description = "Iterate a list using a span with foreach")]
    public void IterateListUsingSpan()
    {
        Span<int> span = CollectionsMarshal.AsSpan(_list);
        foreach (var item in span)
        {

        }
    }

}



Conclusions

Iterating an array using a span actually ran 6% faster than iterating an array without using the span. Iteating a list using a span ran some 50% faster than iterating the list using the span. The code above used the method CollectionsMarshal.AsSpan() in System.Runtime.InteropServices namespace - this has been available since .NET 5.x. Being aware of this method means we can iterate lists about 50% faster keeping in mind that changes to the list after the span was created from the list will not be shown in the span and we could show stale data if changes occur. You can also use this in .NET Framework using System.Memory Nuget package For large lists you know will not change, using spans to iterate could and should promise a large performance improvement. It is also possible to slice a portion of lists or arrays in case it is a case of iterating parts of a large list or array.

No comments:

Post a Comment