DEV Community

Ivan G
Ivan G

Posted on • Originally published at aloneguid.uk

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And the result:

Method Length Mean Error StdDev Min Max Median Gen0 Allocated
EnumerateAsArray 10 3.473 ns 1.4879 ns 0.0816 ns 3.385 ns 3.546 ns 3.490 ns - -
EnumerateWithForeach 10 3.081 ns 0.6979 ns 0.0383 ns 3.057 ns 3.125 ns 3.062 ns - -
EnumerateWithForeachAfterCasting 10 360.151 ns 112.8064 ns 6.1833 ns 353.018 ns 363.983 ns 363.452 ns 0.0648 272 B
EnumerateAsArray 100 49.681 ns 10.2353 ns 0.5610 ns 49.237 ns 50.311 ns 49.495 ns - -
EnumerateWithForeach 100 42.489 ns 15.0294 ns 0.8238 ns 41.728 ns 43.364 ns 42.375 ns - -
EnumerateWithForeachAfterCasting 100 3,365.735 ns 1,017.7344 ns 55.7855 ns 3,330.609 ns 3,430.059 ns 3,336.535 ns 0.5798 2432 B
EnumerateAsArray 1000 404.754 ns 101.6160 ns 5.5699 ns 399.244 ns 410.382 ns 404.638 ns - -
EnumerateWithForeach 1000 366.522 ns 44.1604 ns 2.4206 ns 364.396 ns 369.157 ns 366.015 ns - -
EnumerateWithForeachAfterCasting 1000 34,436.210 ns 16,553.4298 ns 907.3493 ns 33,513.147 ns 35,326.984 ns 34,468.500 ns 5.7373 24032 B
EnumerateAsArray 10000 4,011.964 ns 1,402.6901 ns 76.8862 ns 3,924.527 ns 4,069.007 ns 4,042.358 ns - -
EnumerateWithForeach 10000 3,644.260 ns 350.8573 ns 19.2317 ns 3,632.706 ns 3,666.461 ns 3,633.613 ns - -
EnumerateWithForeachAfterCasting 10000 340,725.798 ns 170,058.5539 ns 9,321.4832 ns 330,179.199 ns 347,861.084 ns 344,137.109 ns 57.1289 240033 B
EnumerateAsArray 100000 40,460.423 ns 9,494.4089 ns 520.4206 ns 40,090.979 ns 41,055.597 ns 40,234.692 ns - -
EnumerateWithForeach 100000 36,107.100 ns 6,022.3198 ns 330.1037 ns 35,901.172 ns 36,487.848 ns 35,932.281 ns - -
EnumerateWithForeachAfterCasting 100000 3,892,458.333 ns 4,019,109.7373 ns 220,300.9666 ns 3,652,532.422 ns 4,085,627.734 ns 3,939,214.844 ns 570.3125 2400038 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  
Enter fullscreen mode Exit fullscreen mode

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  
Enter fullscreen mode Exit fullscreen mode

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  

Enter fullscreen mode Exit fullscreen mode

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 ();
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)