C# – The fastest ways to iterate a List

By | 22/03/2023

In this post, we will see the fastest ways to iterate a List.

We start creating a Console Application called ReadList and then, we add a Class called Core:

[CORE.CS]

public class Core
{
    // Definition of a list used to run the test
    private List<int> _list = new List<int>();

    // Definition of the Costructor where we pass the List's length
    public Core(int maxItem)
    {
        Random generateValue = new Random();
        // Creation of the List used for the test
        _list = Enumerable.Range(1, maxItem).Select(x => generateValue.Next()).ToList();
    }
}


Now, we will add four methods to iterate the List using the statements For, Foreach, List<T>.Foreach and Foreach with CollectionsMarshal.AsSpan:

FOR
(We are not sure that the collection is not modified during the enumeration)

public void ReadWithFor()
{
    for (int i = 0; i < _list.Count; i++)
    {
       if (_list[i] % 2 == 0)
       {
           // doing something
       }
    }
}


FOREACH
(It creates a copy of the collection and so, in this loop, the single ‘item’ is not referencing to the array elements.
There is an internal check that allow us to be sure that the collection is not modified during the enumeration.)

public void ReadWithForEach()
{
    foreach (var item in _list)
    {
        if (item % 2 == 0)
        {
            // doing something
        }
    }
}


LIST<T>.FOREACH
(In every iteration it calls a delegate and this needs more time for the execution compare to a Foreach.)

public void ReadWithListForEach()
{ 
    _list.ForEach(item => {
        if(item % 2 == 0)
        {
           // doing something
        }
     });
 }


COLLECTIONSMARSHAL.ASSPAN
(The CollectionsMarshal.AsSpan return a Span<T> and iterate a Span<T> is very fast.
The only problem is that it is unsafe because, we cannot know if the list is modified during the iteration.)

public void ReadWithForEachUsingSpan()
{
   foreach (var item in CollectionsMarshal.AsSpan(_list))
   {
       if (item % 2 == 0)
       {
          // Doing something
       }
   }
}


Now, in order to run the benchmarks to verify the methods, we add another class called BenchmarkCore so defined:
[BENCHMARKCORE.CS]

using BenchmarkDotNet.Attributes;

namespace ReadList;

[MemoryDiagnoser]
[Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)]
[RankColumn]
public class BenchmarkCore
{
    [Benchmark]
    [Arguments(100)]
    [Arguments(1000)]
    [Arguments(5000)]
    [Arguments(10000)]
    public void CheckReadWithListForEachMethod(int inputValue)
    {
        Core objCore = new Core(inputValue);
        objCore.ReadWithListForEach();
    }

    [Benchmark]
    [Arguments(100)]
    [Arguments(1000)]
    [Arguments(5000)]
    [Arguments(10000)]
    public void CheckReadWithForEachMethod(int inputValue)
    {
        Core objCore = new Core(inputValue);
        objCore.ReadWithForEach();
    }

    [Benchmark]
    [Arguments(100)]
    [Arguments(1000)]
    [Arguments(5000)]
    [Arguments(10000)]
    public void CheckReadWithForMethod(int inputValue)
    {
        Core objCore = new Core(inputValue);
        objCore.ReadWithFor();
    }

    [Benchmark]
    [Arguments(100)]
    [Arguments(1000)]
    [Arguments(5000)]
    [Arguments(10000)]
    public void CheckReadWithForEachUsingSpanMethod(int inputValue)
    {
        Core objCore = new Core(inputValue);
        objCore.ReadWithForEachUsingSpan();
    }
}


and then, we modify the Program.cs file:
[PROGRAM.CS]

using BenchmarkDotNet.Running;
using ReadList;

Console.WriteLine("Start Benchmark");

BenchmarkRunner.Run<BenchmarkCore>();


Now, we can run the benchmarks:


Finally, we will run the same methods but using a list of string

[CORE.CS]

using System.Runtime.InteropServices;

namespace ReadList;

public class Core
{
    private List<string> _list = new List<string>();
    public Core(int maxItem)
    {
        Random generateValue = new Random();
        _list = Enumerable.Range(1, maxItem).Select(x => generateValue.Next().ToString()).ToList();
    }

    public void ReadWithListForEach()
    { 
        _list.ForEach(item => {
            if(item.Length % 2 == 0)
            {
                // doing something
            }
        });
    }

    public void ReadWithForEach()
    {
        foreach (var item in _list)
        {
            if (item.Length % 2 == 0)
            {
                // doing something
            }
        }
    }

    public void ReadWithFor()
    {
        for (int i = 0; i < _list.Count; i++)
        {
            if (_list[i].Length % 2 == 0)
            {
                // doing something
            }
        }
    }

    public void ReadWithForEachUsingSpan()
    {
        foreach (var item in CollectionsMarshal.AsSpan(_list))
        {
            if (item.Length % 2 == 0)
            {
                // doing something
            }
        }
    }
}


Now, if we run the benchmarks, these will be the results:


In both benchmarks, we can see that the worst method is the ReadWithListForEach instead, the other three methods, have similar results.


Category: C# Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *