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)