To foreach or not to foreach that is the question.


Recently email was forwarded to me with a link to a page with some performance tips for developers.  The second performance tip on the page was:







foreach


foreach through an array is incredibly slow compared to for (int i = 0; i < array.Length; i)


This one leapt out at me because it is well … not to put too fine a point on it … wrong!  (at least that is what I thought).  C# and Visual Basic 7 both contain specific optimizations to ensure that  foreach on arrays perform equivalently to for(int i=0; i < array.Length; i); I have written plenty of code assuming foreach and for(int i; …) were equivalent.  To verify my assumption and to ensure that the code I wrote was as efficient as possible in the future I did a little experiment: I wrote a small C# application with 2 functions the first of which enumerated an array of integers using foreach, the second enumerated an array of integers using for(int …).  I then used ildasm to disassemble the compiled binary to IL and then examined the resulting code.


Test code:





using System;

class MyClass
{
    static void One()
    {
        int[] a = new int[10000];
        foreach(int i in a)
            Console.WriteLine(i);
    }
    static void Two()
    {
        int[] a = new int[10000];
        for(int j=0; j<a.Length; j++)
       {
            int i = a[j];
            Console.WriteLine(i);
        }
    }
}

I saved this little program in to a c# source file called fe.cs and compiled it using the command line:



csc /t:library fe.cs






The .Net Framework sdk is installed with Visual Studio 7.0 and 7.1 or it can be downloaded from microsoft at: http://msdn.microsoft.com/netframework/downloads/updates/default.aspx .   The SDK contains a nifty utility called ildasm.exe.  If you have Visual Studio 7 or 7.1 installed then you already have the sdk installed.


Ildasm rather cleverly operates as either a command line tool or as a GUI depending.  For my purposes the command line was all that I required.  I created an IL file using the command (you may need to ensure that the path is set to the “Program Files\Microsoft.NET\SDK\v1.1\Bin” directory:



ildasm fe.dll /out:fe.il /text


then in an editor I examined this file’s details, the interesting part for me is the method bodies for One (foreach use) and Two(for(int; ..) use.


















    static void One()
    {
        int[] a = new int[10000];
        foreach(int i in a)
            Console.WriteLine(i);
    }
  static void Two()
    {
        int[] a = new int[10000];
        for(int j=0; j<a.Length; j++)
       {
            int i = a[j];
            Console.WriteLine(i);
        }
    }
.method private hidebysig static void One() cil managed
{
// Code size 38 (0x26)
.maxstack 2
.locals init (int32[] V_0,
int32 V_1,
int32[] V_2,
int32 V_3)
.method private hidebysig static void Two() cil managed
{
// Code size 36 (0x24)
.maxstack 2
.locals init (int32[] V_0,
int32 V_1,
int32 V_2)
IL_0000: ldc.i4 0x2710
IL_0005: newarr [mscorlib]System.Int32
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: stloc.2
IL_000d: ldc.i4.0
IL_000e: stloc.3
IL_000f: br.s IL_001f
IL_0000: ldc.i4 0x2710
IL_0005: newarr [mscorlib]System.Int32
IL_000a: stloc.0
IL_000b: ldc.i4.0
IL_000c: stloc.1
IL_000d: br.s IL_001d

IL_0011: ldloc.2
IL_0012: ldloc.3
IL_0013: ldelem.i4
IL_0014: stloc.1
IL_0015: ldloc.1
IL_0016: call void [mscorlib]System.Console::WriteLine(int32)
IL_001b: ldloc.3
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.3
IL_001f: ldloc.3
IL_0020: ldloc.2
IL_0021: ldlen
IL_0022: conv.i4
IL_0023: blt.s IL_0011
IL_000f: ldloc.0
IL_0010: ldloc.1
IL_0011: ldelem.i4
IL_0012: stloc.2
IL_0013: ldloc.2
IL_0014: call void [mscorlib]System.Console::WriteLine(int32)
IL_0019: ldloc.1
IL_001a: ldc.i4.1
IL_001b: add
IL_001c: stloc.1
IL_001d: ldloc.1
IL_001e: ldloc.0
IL_001f: ldlen
IL_0020: conv.i4
IL_0021: blt.s IL_000f
IL_0025: ret
} // end of method MyClass::One

IL_0023: ret
} // end of method MyClass::Two

If you examine the IL for each version of the function you will see that the red is basic variable initialization, creating the array setting loop counters to zero, that kind of thing.  The bold blue text is the body of the loop, close examination shows that they are effectively identical.  Which confirms my original belief that there is no performance benefit to be gained by replacing foreach(…) with for(int i; …).


There are circumstances where foreach() introduces a performance penalty; but when the compiler can statically determine that the collection is infact an array, then foreach performs exactly the same as the equivalent hand coded loop.


I hope to visit managed collections again in the future.


 


 


Comments (18)

  1. Paul Wilson says:

    I’m glad to hear that’s true for arrays, but it certainly is NOT true for all collections. I found that foreach loops over HashTables and ArrayLists were rather "slow" compared to simple for loops, which made a big difference.

  2. Kevin Ransom says:

    Indeed, I hope to spend some time talking about that in a future entry.

  3. That is a huge blanket statement. If you are using an ArrayList, with value types you would have huge perf consequences due to boxing.

    I have heard a similar statement (foreach is slower than for ()) I guess it just depends on the context.

  4. Yup, I posted on Eric Gunnersons blog about what Paul mentions, and I had a long running ‘debate’ about this on the ASP.NET perf forums. The most useful tool I’ve found for doing these comparisons (without digging through the IL) is Reflector – the C# output is pretty revealing in these debates.

  5. matthew says:

    you are right. Foreach is not necessarily better or worse from a performance point of view.

    But THE most important thing, is that unless you have a specific need to optimize, foreach is just more elegant. If you’re only iterating through a few items, who cares either way?

    Use foreach unless you have a very good reason not to.

  6. Joshua Allen says:

    These are not identical. Get rid of the int i = a[j]; in the second one and ildasm and you save two instructions inside the loop. You can save yet another two instructions by avoiding checking < a.Length (set int len = a.Length at the start, and compare len). I just ildasm’d all three examples myself. The foreach is the worst of all. And this example is the most efficient possible foreach loop, since the compiler optimizes for it and normal cases would require IEnumerable. I personally thing that the while < a.Length is the *worst* option of the three in the *general* case, because you have no freekin’ clue what is happening in that property access. FX guidelines say to not do heay work in a getter, but I have seen plenty of violations.

  7. Jason Coyne says:

    Actually, you do not want to set the length into a temporary variable. In a for loop, where the length is set as the end condition of the loop, the IL does some optimizations to stop checking for out of bounds errors, because by defenition you are not out of bounds.

    When you put the length into a temp variable, it must do those checks on each iteration. Brad A. has done an extensive analysis of this issue.

  8. <p>In his blog, <span style="font-style: italic;">Better Living Through Software</span>, Joshua Allen looks at three different ways to loop in C# from an optimizing-for-performance point of view:</p><p><code>foreach (int i in foo) {}<br>

    <br>

    for (int i = 0; i &lt; foo.Length; i++) {}<br>

    <br>

    int len = foo.Length; for (int i = 0; …

  9. Here are some resources to read up on the differences between ‘for’ and ‘foreach’.

    Enumerating array…

  10. holyrong says:

    for和foreach 的效率问题是个老问题了,从网上看到的是众说纷纭,有说for效率高的也有说foreach效率高的,还有说测试方法有问题的;通过实验发现for的效率比foreach高。

  11. cnblogs.com says:

    事实上对Array进行foreach的时候根本不会调用GetEnumerator()方法, 也就是被编译器给内联了. 而对于List&lt;&gt;, 虽然没有编译器内联GetEnumerator()

  12. Although very handy, C#&#39;s foreach statement is actually quite dangerous. In fact, I may swear off its use entirely. Why? Two reasons: (1) performance, and (2) predictability….