Is It Faster to Enumerate an Array With "foreach" or "for" in C#?

Here’s an interesting question - having an array of integers, is it faster to enumerate it, to perform a simple operation on an element, with for or foreach?

To answer this, first of all I’ve created a benchmark that covers 3 use cases:

  1. Use for loop and get an element by it’s index.
  2. Use foreach on the array.
  3. Use foreach on the array, but before enumerating case the array to IEnumerable forcibly. I don’t know why I had a hunch that might behave slightly different, but I did it anyway.

Here is the testing code:

#LINQPad optimize+

void Main()
{
	Util.AutoScrollResults = true;
	BenchmarkRunner.Run<Enumeration>();
}

[ShortRunJob]
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
[MemoryDiagnoser]
[MarkdownExporter]
public class Enumeration
{
    int[] _source;
    
	[Params(10, 100, 1000, 10000, 100000)]
	public int Length;


	[GlobalSetup]
	public void Setup()
	{
		_source = Enumerable.Range(0, Length).ToArray();
	}


	[Benchmark]
	public void EnumerateAsArray()
	{
        int count = 0;
        for(int i = 0; i < Length; i++) {
            int element = _source[i];
            count += 1;
        }
	}

	[Benchmark]
	public void EnumerateWithForeach()
	{
        int count = 0;
        foreach(int i in _source) {
            count += 1;
        }
	}

    [Benchmark]
    public void EnumerateWithForeachAfterCasting() {
        int count = 0;
        foreach (int i in (IEnumerable)_source) {
            count += 1;
        }
    }
}

And the result:

MethodLengthMeanErrorStdDevMinMaxMedianGen0Allocated
EnumerateAsArray103.473 ns1.4879 ns0.0816 ns3.385 ns3.546 ns3.490 ns--
EnumerateWithForeach103.081 ns0.6979 ns0.0383 ns3.057 ns3.125 ns3.062 ns--
EnumerateWithForeachAfterCasting10360.151 ns112.8064 ns6.1833 ns353.018 ns363.983 ns363.452 ns0.0648272 B
EnumerateAsArray10049.681 ns10.2353 ns0.5610 ns49.237 ns50.311 ns49.495 ns--
EnumerateWithForeach10042.489 ns15.0294 ns0.8238 ns41.728 ns43.364 ns42.375 ns--
EnumerateWithForeachAfterCasting1003,365.735 ns1,017.7344 ns55.7855 ns3,330.609 ns3,430.059 ns3,336.535 ns0.57982432 B
EnumerateAsArray1000404.754 ns101.6160 ns5.5699 ns399.244 ns410.382 ns404.638 ns--
EnumerateWithForeach1000366.522 ns44.1604 ns2.4206 ns364.396 ns369.157 ns366.015 ns--
EnumerateWithForeachAfterCasting100034,436.210 ns16,553.4298 ns907.3493 ns33,513.147 ns35,326.984 ns34,468.500 ns5.737324032 B
EnumerateAsArray100004,011.964 ns1,402.6901 ns76.8862 ns3,924.527 ns4,069.007 ns4,042.358 ns--
EnumerateWithForeach100003,644.260 ns350.8573 ns19.2317 ns3,632.706 ns3,666.461 ns3,633.613 ns--
EnumerateWithForeachAfterCasting10000340,725.798 ns170,058.5539 ns9,321.4832 ns330,179.199 ns347,861.084 ns344,137.109 ns57.1289240033 B
EnumerateAsArray10000040,460.423 ns9,494.4089 ns520.4206 ns40,090.979 ns41,055.597 ns40,234.692 ns--
EnumerateWithForeach10000036,107.100 ns6,022.3198 ns330.1037 ns35,901.172 ns36,487.848 ns35,932.281 ns--
EnumerateWithForeachAfterCasting1000003,892,458.333 ns4,019,109.7373 ns220,300.9666 ns3,652,532.422 ns4,085,627.734 ns3,939,214.844 ns570.31252400038 B

The findings are impressive - enumerating an array with for or foreach is not much different, but surprisingly foreach is slightly faster (around 12%). Whereas when casting to IEnumerable and then performing iteration is way way slower than anything else! In fact, it’s about 107 times slower than normal array iteration!

To understand what’s happening, I’m going to disassembly the generated code.

EnumerateAsArray

This is IL representation:

IL_0000  ldc.i4.0   
IL_0001  stloc.0     // count 
IL_0002  ldc.i4.0   
IL_0003  stloc.1     // i 
IL_0004  br.s  IL_0017 
IL_0006  ldarg.0   
IL_0007  ldfld  Enumeration._source 
IL_000C  ldloc.1     // i 
IL_000D  ldelem.i4   
IL_000E  pop   
IL_000F  ldloc.0     // count 
IL_0010  ldc.i4.1   
IL_0011  add   
IL_0012  stloc.0     // count 
IL_0013  ldloc.1     // i 
IL_0014  ldc.i4.1   
IL_0015  add   
IL_0016  stloc.1     // i 
IL_0017  ldloc.1     // i 
IL_0018  ldarg.0   
IL_0019  ldfld  Enumeration.Length 
IL_001E  blt.s  IL_0006 
IL_0020  ret  

As you can see, this is what’s happening:

  1. Create two variables - count and i.
  2. Keep comparing array length to i (IL_0017). When i is less than array length, jump to IL_0006.
  3. Load array element.
  4. Increment the count.
  5. Increment i.

Simple enough.

EnumerateWithForeach

IL source:

IL_0000  ldc.i4.0   
IL_0001  stloc.0     // count 
IL_0002  ldarg.0   
IL_0003  ldfld  Enumeration._source 
IL_0008  stloc.1   
IL_0009  ldc.i4.0   
IL_000A  stloc.2   
IL_000B  br.s  IL_0019 
IL_000D  ldloc.1   
IL_000E  ldloc.2   
IL_000F  ldelem.i4   
IL_0010  pop   
IL_0011  ldloc.0     // count 
IL_0012  ldc.i4.1   
IL_0013  add   
IL_0014  stloc.0     // count 
IL_0015  ldloc.2   
IL_0016  ldc.i4.1   
IL_0017  add   
IL_0018  stloc.2   
IL_0019  ldloc.2   
IL_001A  ldloc.1   
IL_001B  ldlen   
IL_001C  conv.i4   
IL_001D  blt.s  IL_000D 
IL_001F  ret  

It’s not much different to before, but .NET runtime makes slight optimisation, because it knows you are going to need to value and not need the index (foreach always retrieves the value). So that’s good!

EnumerateWithForeachAfterCasting

IL source:

IL_0000  ldc.i4.0   
IL_0001  stloc.0     // count 
IL_0002  ldarg.0   
IL_0003  ldfld  Enumeration._source 
IL_0008  callvirt  IEnumerable.GetEnumerator () 
IL_000D  stloc.1   
IL_000E  br.s  IL_0020 
IL_0010  ldloc.1   
IL_0011  callvirt  IEnumerator.get_Current () 
IL_0016  unbox.any  Int32 
IL_001B  pop   
IL_001C  ldloc.0     // count 
IL_001D  ldc.i4.1   
IL_001E  add   
IL_001F  stloc.0     // count 
IL_0020  ldloc.1   
IL_0021  callvirt  IEnumerator.MoveNext () 
IL_0026  brtrue.s  IL_0010 
IL_0028  leave.s  IL_003B 
IL_002A  ldloc.1   
IL_002B  isinst  IDisposable 
IL_0030  stloc.2   
IL_0031  ldloc.2   
IL_0032  brfalse.s  IL_003A 
IL_0034  ldloc.2   
IL_0035  callvirt  IDisposable.Dispose () 
IL_003A  endfinally   
IL_003B  ret  

Wow! You don’t need to be an IL expect to see this will be slow, because it’s full of callvirt calls. Calling virtual methods is expensive! This time you are forcing C# to use IEnumerable pattern, and that’s exactly what it does. To represent this code in C# 1, it translates to the following:

int count = 0;
IEnumerator enumerator = ((IEnumerable)_source).GetEnumerator ();
try
{
    while (enumerator.MoveNext ())
    {
        int num = (int)enumerator.Current;
        count++;
    }
}
finally
{
    IDisposable disposable = enumerator as IDisposable;
    if (disposable != null)
    {
        disposable.Dispose ();
    }
}

So a lot of method calls and unboxing, all are extremely expensive.

Summary

  1. If you can, prefer foreach to for loops, they will be slightly faster.
  2. Never cast arrays to IEnumerable and then enumerate, as this expands into a lot of virtual calls using IEnumerator pattern.


To contact me, send an email anytime or leave a comment below.