导航

深入了解 foreach 和 for 循环到底有哪些不同 (一)

Posted on 2008-08-20 21:14  BitsBlue  阅读(2650)  评论(14)    收藏  举报

看到有些博客谈到for 循环的性能要好于 foreach。那么到底是不是呢?如果是的话,究竟两者之间有什么区别? Jitted的代码可能是最直接的方式来了解两者的差异。

 

例子代码:

namespace Test
{
    class Sample
    {
        private static int counter;

        public int Value = counter++;
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<Sample> samples = new List<Sample>()
            {
                new Sample(),
                new Sample(),
                new Sample()
            };

            ForeachTest(samples);

            ForTest(samples);

            Console.Read();
        }

        private static void ForeachTest(List<Sample> samples)
        {
            foreach (Sample item in samples)
            {
                Console.WriteLine(item.Value);
            }
        }

        private static void ForTest(List<Sample> samples)
        {
            for (int i = 0; i < samples.Count; i++)
            {
                Console.WriteLine(samples[i].Value);
            }
        }
    }

}

 

环境准备:

新建一个c#命令行工程,然后拷贝例子代码。

选择Release模式。

Ctrl+F5在VS环境直接运行程序但不进入调试模式。

打开windbg, F6来attach刚才运行起来的程序。


Windbg:

.loadby sos mscorwks 加载sos

 

先来看一下 for 循环到底有什么。

0:000> !name2ee test Test.Program.ForTest
Module: 00192c5c (Test.exe)
Token: 0x06000004
MethodDesc: 00193014
Name: Test.Program.ForTest(System.Collections.Generic.List`1<Test.Sample>)

JITTED Code Address: 00a90208

 

反编译出ForTest的jitted代码,方便大家阅读我已经放入了较详细的注释。

0:000> !u 00a90208
Normal JIT generated code
Test.Program.ForTest(System.Collections.Generic.List`1<Test.Sample>)
Begin 00a90208, size 4d
>>> 00a90208 55              push    ebp
004001f9 8bec            mov     ebp,esp
004001fb 57              push    edi
004001fc 56              push    esi
004001fd 53              push    ebx
004001fe 8bd9            mov     ebx,ecx  //ecx 保存 samples 对象
00400200 33ff            xor     edi,edi  //edi 清零
00400202 8b430c          mov     eax,dword ptr [ebx+0Ch]  //设置List的大小到eax,就是3
00400205 85c0            test    eax,eax
00400207 7e31            jle     0040023a //如果上句测试失败,跳转到结束
00400209 3bf8            cmp     edi,eax  //循环的开始。进行循环比较,edi是当前的相等于变量i的值,eax是3
0040020b 7205            jb      00400212 //如果已经超过范围,直接抛出OutOfRangeException,否则跳过下一句
*** WARNING: Unable to verify checksum for C:"Windows"assembly"NativeImages_v2.0.50727_32"mscorlib"9adb89fa22fd5b4ce433b5aca7fb1b07"mscorlib.ni.dll
0040020d e872a6bc66      call    mscorlib_ni+0x68a884 (66fca884) (System.ThrowHelper.ThrowArgumentOutOfRangeException(), mdToken: 060000d9)
00400212 8b4304          mov     eax,dword ptr [ebx+4] //传送List对象内部数组的地址到eax
00400215 3b7804          cmp     edi,dword ptr [eax+4] //范围校验
00400218 7325            jae     0040023f //失败跳转到JIT_RngChkFail
0040021a 8b44b80c        mov     eax,dword ptr [eax+edi*4+0Ch] //传送第i个sample对象的地址到eax
0040021e 8b7004          mov     esi,dword ptr [eax+4] //传送第i Sample个对象的Value值进入esi
00400221 e87ad07666      call    mscorlib_ni+0x22d2a0 (66b6d2a0) (System.Console.get_Out(), mdToken: 06000772)
00400226 8bc8            mov     ecx,eax //传送 Console.Out 地址到 ecx
00400228 8bd6            mov     edx,esi //传送第i Sample个对象的Value值进入 edx
0040022a 8b01            mov     eax,dword ptr [ecx] //传送Console.Out 的method table到 eax
0040022c ff90bc000000    call    dword ptr [eax+0BCh] //打印Sample.Value到控制台程序
00400232 47              inc     edi //edi加1
00400233 8b430c          mov     eax,dword ptr [ebx+0Ch] //设置List的大小到eax,就是3
00400236 3bc7            cmp     eax,edi //检查是否需要继续循环
00400238 7fcf            jg      00400209 //跳转到继续循环代码处如果上步为真
0040023a 5b              pop     ebx
0040023b 5e              pop     esi
0040023c 5f              pop     edi
0040023d 5d              pop     ebp
0040023e c3              ret
00a9024f e868be7067      call    mscorwks!JIT_RngChkFail (6819c0bc)
00a90254 cc              int     3


从代码中可以看到,循环开始处进行一次比较,如果失败则抛出OutOfRangeException。最后在结尾处也进行一次判断来决定是否继续循环。当中的代码比较直观,基本上就是取值然后打印输出。当然还有inc来累加计数。

 

下一次,我们来看看ForeachTest到底有些什么。哪些和ForTest不一样。