Amazon.com Widgets

Enumerating array using for vs. foreach

Interesting question over an internal alias today…

 

Questions:

What is the difference between enumerating an array using for versus foreach ?

What is the recommended practice and why?

 

Here is an example:

Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

 

for (int i=0; i<assemblies.Length; i++)

      DoSomething(assemblies[i]);

 

foreach (Assembly a in assemblies)

      DoSomething(a);

 

 

Answer (from one of the perf architects on the CLR team):

The code gen is basically identical... guidance is to use foreach for clarity unless you plan to modify the contents of the array as you go, in which case for is required.  Enumerators are not created in the array case.  Note this analysis is not applicable to the collection class case, this is for arrays.

 

For more details see the "Enumeration Overhead" section in http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt05.asp -- searching for "foreach" in that same document will give other illustrations and guidance.

 

using System;

class Test
{
 static void Main(String[] args)
 {
  for (int i=0; i<args.Length;i++)
   Console.WriteLine(args[i]);

 

  foreach (String s in args)
   Console.WriteLine(s);
 }
}

 

 

// standard prolog

G_M001_IG01:


IN0038: 000000      push    EDI
IN0039: 000001      push    ESI
IN0040: 000002      push    EBX
IN0041: 000003      mov     EDI, ECX  
// the array gets stored in EDI on a long term basis

 

G_M001_IG02:  ; offs=000005H

 

// this is the "for" case

// this is all a redundant length check as far as I can tell

G_M001_IG02:
IN0001: 000005      xor     EBX, EBX
// EBX is holding 'i' -- starts at zero
IN0002: 000007      cmp     dword ptr [EDI+4], 0 
// checking length of array to see if it is negative, it won't be
IN0003: 00000B      jle     SHORT G_M001_IG06

 

G_M001_IG03:  ; offs=00000DH

 

G_M001_IG03:
IN0004: 00000D      cmp     EBX, dword ptr [EDI+4]
// check the loop variable against array length
IN0005: 000010      jae     SHORT G_M001_IG11
IN0006: 000012      mov     ESI, gword ptr [EDI+4*EBX+12]
// fetch the string from the array
IN0007: 000016      cmp     gword ptr [classVar[0x5b9d1994]], 0
// test if the stream has been initialized
IN0008: 00001D      jne     SHORT G_M001_IG05

 

G_M001_IG04:  ; offs=00001FH

 

G_M001_IG04:
IN0009: 00001F      mov     ECX, 1
IN0010: 000024      call    System.Console.InitializeStdOutError(bool)
// initialize stream

 

G_M001_IG05:  ; offs=000029H

 

G_M001_IG05:
IN0011: 000029      mov     ECX, gword ptr [classVar[0x5b9d1994]]
// we now have the power to get the stdout stream into ECX
IN0012: 00002F      mov     EDX, ESI
// get the string we saved before set it up as an arg
IN0013: 000031      call    dword ptr [(reloc 0x3a80014)]System.IO.TextWriter.WriteLine(ref)
// write the string
IN0014: 000037      add     EBX, 1
// ebx has 'i' in it remember, upcount
IN0015: 00003A      cmp     dword ptr [EDI+4], EBX
// test the current 'i' against the length
IN0016: 00003D      jg      SHORT G_M001_IG03
//loop until done

 

G_M001_IG06:  ; offs=00003FH

 

// this is the "foreach" case

G_M001_IG06:
IN0017: 00003F      xor     ESI, ESI
// ESI is holding the loop index -- starts at zero -- will not hurt codegen at all
IN0018: 000041      cmp     dword ptr [EDI+4], 0
// same redundant length check
IN0019: 000045      jle     SHORT G_M001_IG10

 

G_M001_IG07:  ; offs=000047H

 

G_M001_IG07:
IN0020: 000047      cmp     ESI, dword ptr [EDI+4]
// testing index before entering loop as above
IN0021: 00004A      jae     SHORT G_M001_IG11
IN0022: 00004C      mov     EBX, gword ptr [EDI+4*ESI+12]
IN0023: 000050      cmp     gword ptr [classVar[0x5b9d1994]], 0
// testing if we need to run the .cctor as above
IN0024: 000057      jne     SHORT G_M001_IG09

 

G_M001_IG08:  ; offs=000059H

 

G_M001_IG08:
IN0025: 000059      mov     ECX, 1
IN0026: 00005E      call    System.Console.InitializeStdOutError(bool)
// call inlined body of .cctor as above

 

G_M001_IG09:  ; offs=000063H

 

G_M001_IG09:
IN0027: 000063      mov     ECX, gword ptr [classVar[0x5b9d1994]]
// get stream and write string as above
IN0028: 000069      mov     EDX, EBX
IN0029: 00006B      call    dword ptr [(reloc 0x3a8002c)]System.IO.TextWriter.WriteLine(ref)
IN0030: 000071      add     ESI, 1
IN0031: 000074      cmp     dword ptr [EDI+4], ESI
// same loop dynamics as above only using ESI instead of EBX
IN0032: 000077      jg      SHORT G_M001_IG07

 

G_M001_IG10:  ; offs=000079H

 

G_M001_IG10:
IN0033: __epilog:
        000079      pop     EBX
// we are so outta here
        00007A      pop     ESI
        00007B      pop     EDI
IN0034: 00007C      ret    

 

 

Notice that this is very specific for arrays (and in V1.1 and above enumerating over strings), this does not apply to enumerating over collections generally…  Kevin has a good post on that…

 

Published 29 April 04 10:30 by BradA
Filed under:

Comments

# Rico Mariani's WebLog said on April 29, 2004 1:45 PM:
# Russ C said on April 29, 2004 12:05 PM:
Interesting post, but to gain Clarification ... I was under the impression that it is better to use For Loops of Foreach as a For loop can bypass a boundary check being emitted for every iteration ?

Thanks.
# Russ C said on April 29, 2004 12:09 PM:
Oops read the code and thats whats happening , although as far as the CLR is concerned I don't think the check is redundant, as logically a foreach doesn't know its at the end till it gets there ...
# mikeb said on April 29, 2004 1:34 PM:
I'd like to know how you got the JITted x86 asm. Is there a relatively easy way to do this?

I've tried doing this using cordbg, but I find it a very painful experience. Any tips?
# Johan Ericsson said on April 29, 2004 3:38 PM:
I'd like to be able to foreach over an array in the reverse direction. I don't think there is any easier way than trying to get the for(...) loop correct...
# Russ C said on April 30, 2004 1:31 PM:
mikeb : put a breakpoint into your program in VS, when it happens go into debug and choose Assembly ... you can even put break codes in the assembly :)
# Josh Williams said on May 2, 2004 9:00 PM:
Mike -- You can also use windbg/cdb with the SOS extension... If you can get an IP within the method that you're interested in inspecting you can get the disassembly using "!u <address>". SOS will do it's best to pretty print the results (adding info to call sites where they can be resolved, and such...).
# Nick's Delphi Blog said on May 4, 2004 10:47 AM:
# 仪表 said on July 1, 2004 1:57 AM:
I'd like to know how you got the JITted x86 asm. Is there a relatively easy way to do this?

I've tried doing this using cordbg, but I find it a very painful experience. Any tips?
# Ring said on July 31, 2004 4:22 AM:
Should be for example like this, the understanding that it could be better to give an example, because my understanding ability is bad, don't laugh at me ! !
# - TrIpLeZoNe - said on June 7, 2005 4:10 AM:
Here are some resources to read up on the differences between 'for' and 'foreach'.
Enumerating array...
# 玉开 said on March 15, 2007 1:08 AM:

for和foreach的效率问题是个老问题了,从网上看到的是众说纷纭,有说for效率高的也有说foreach效率高的,还有说测试方法有问题的;鉴于此,我就自己做了个试验证明一下,然后探究一下可能的原...

# Kris - TECH said on March 15, 2007 1:12 AM:

for和foreach 的效率问题是个老问题了,从网上看到的是众说纷纭,有说for效率高的也有说foreach效率高的,还有说测试方法有问题的;鉴于此,我就自己做了个试验证明一下,然后探究一下可能的原因。

# holyrong said on July 27, 2007 12:30 PM:

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

# C# compiler fails to optimize for loop same as foreach | keyongtech said on January 21, 2009 10:07 PM:

PingBack from http://www.keyongtech.com/640444-c-compiler-fails-to-optimize

# For ... Each en partant de la fin ? - Page 2 | hilpers said on January 22, 2009 9:43 AM:

PingBack from http://www.hilpers.fr/931652-for-each-en-partant-de/2

New Comments to this post are disabled

Search

This Blog

Syndication

Page view tracker