《Effective C#》是基于.Net 1.0版本写的

今天上园子,看到了一篇C#中for和foreach循环的性能 ,对其中的三个

        int[] foo = new int[100];

          1,  foreach (int i in foo)
                Console.WriteLine(i.ToString());

          2,for(int index=0;index<foo.Length;index++)
                Console.WriteLine(foo[index].ToString());

          3,int len=foo.Length;
            for(int index=0;index<len;index++)
                Console.WriteLine(foo[index].ToString());
的解释让我比较怀疑,后来看到作者是看的《Effective C#》,我也翻了一下,但是很快我就翻到前面的前言部分,后来发现《Effective C#》的作者是基于.Net 1.0做的经验总结,现在看来他的解释是有些问题的。

Effective C#中文版前言

关于C# 2.0

我之所以对新的C
# 2.0版本所言甚少,有两个原因。首先,本书中的大部分建议也同样适用于C# 2.0。虽然C# 2.0是一个非常重大的升级版本,但它是建立在C# 1.0基础之上的,且并没有让现如今的建议失效。对于最佳实践有可能发生变化的地方,我已经在文中给出了说明。

第二个原因是现在编写新的C
# 2.0功能的高效用法还为时过早。本书的内容是基于我以及我的同事使用C# 1.0的已有经验。我们对于C#2.0中的新功能还没有足够的经验,因而也就不了解能够应用到日常任务中的最佳做法。当在书中编写C# 2.0新功能的高效用法的时机还未成熟时,我并不想误导读者。

 

测试源码比较简单:

 

 1     static int[] foo = new int[100];
 2     static void Foo()
 3     {
 4         foreach (int i in foo)
 5             Console.WriteLine (i);
 6         //for (int index = 0; index < foo.Length; index++)
 7         //    ;
 8 
 9         //int len = foo.Length;
10         //for (int index = 0; index < len; index++)
11         //    ;
12     }
13     static void Main(string[] args)
14     {
15         Foo();
16     }

 

下面是我自己的分析,先看第一个的IL:

 

 1 .method private hidebysig static void Foo() cil managed
 2 {
 3     .maxstack 2
 4     .locals init (
 5         [0int32 i,存储每次循环的数组元素
 6         [1int32[] CS$6$0000,指向要访问的数组
 7         [2int32 CS$7$0001,数组的访问索引
 8         [3bool CS$4$0002)用于判断是否数组越界
 9     L_0000: nop 
10     L_0001: nop 
11     L_0002: ldsfld int32[] Class1::foo将数组foo推上堆栈
12     L_0007: stloc.1 存储在CS$6$0000
13     L_0008: ldc.i4.0 推入一个int32的值0
14     L_0009: stloc.2 存储在CS$7$0001
15     L_000a: br.s L_0014无条件转移到L_0014处
16     L_000c: ldloc.1将CS$6$0000推上堆栈
17     L_000d: ldloc.2将CS$7$0001推上堆栈
18     L_000e: ldelem.i4 根据CS$6$0000将所索引为CS$7$0001的值推向栈顶
19     L_000f: stloc.0 存入i
20     L_0010: ldloc.2将CS$7$0001推上堆栈
21     L_0011: ldc.i4.1 将1推上堆栈
22     L_0012: add CS$7$0001+1
23     L_0013: stloc.2 存储回CS$7$0001
24     L_0014: ldloc.2 将CS$7$0001推上堆栈
25     L_0015: ldloc.1 将CS$6$0000推上堆栈
26 L_0016: ldlen 推入数组的长度
27 L_0017: conv.i4 将栈顶的值装为Int32
28     L_0018: clt 比较CS$7$0001和当前栈顶数组的长度;如果没有越界,则将1推向栈  顶,反之推0
29     L_001a: stloc.3 将比较结果存储在CS$4$0002
30     L_001b: ldloc.3 将CS$4$0002推向栈顶
31     L_001c: brtrue.s L_000c如果CS$4$0002 为true、非空或非零,则跳转到L_001c
32     L_001e: ret 
33 }
34 

 

接着是汇编码:

 

 1 69:         foreach (int i in foo)
 2 0000003f  nop              
 3 00000040  mov         eax,dword ptr ds:[02D584C0h] 将数组foo推上堆栈
 4 00000045  mov         dword ptr [ebp-40h],eax  存储在CS$6$0000
 5 00000048  xor         edx,edx  推入一个int32的值0
 6 0000004a  mov         dword ptr [ebp-44h],edx  存储在CS$7$0001
 7 0000004d  nop              
 8 0000004e  jmp         0000006A   无条件转移到L_0014处
 9 00000050  mov         eax,dword ptr [ebp-44h] 将CS$6$0000推上堆栈
10 00000053  mov         edx,dword ptr [ebp-40h] 将CS$7$0001推上堆栈
11 00000056  cmp         eax,dword ptr [edx+4] 比较CS$7$0001和当前数组的长度
12 00000059  jb          00000060 无越界则跳转到00000060
13 0000005b  call        6B49AD84 这个地方是call越界异常??
14 00000060  mov         eax,dword ptr [edx+eax*4+8] 根据CS$6$0000将所索引为CS$7$0001的值推向栈顶
15 00000064  mov         dword ptr [ebp-3Ch],eax 存入i
16 00000067  inc         dword ptr [ebp-44h] CS$7$0001+1
17 0000006a  mov         eax,dword ptr [ebp-44h]  将CS$7$0001推上堆栈
18 0000006d  mov         edx,dword ptr [ebp-40h] 将CS$6$0000推上堆栈
19 00000070  cmp         eax,dword ptr [edx+4]  比较CS$7$0001和当前数组的长度
20 00000073  setl        al   如果没有越界,则将1推向栈顶,反之推0
21 00000076  movzx       eax,al 
22 00000079  mov         dword ptr [ebp-48h],eax将比较结果存储在CS$4$0002
23 0000007c  cmp         dword ptr [ebp-48h],0将CS$4$0002推向栈顶,如果CS$4$0002 为true、非空或非零,则跳转到L_001c
24 00000080  jne         00000050 
25 70:             ;
26 

 

可以看出foreach是会在运行过程中检查数组是否越界的,汇编码的00000050到00000059很好的说明了。

下面是第2个循环的IL:

 

 1 .method private hidebysig static void Foo() cil managed
 2 {
 3     .maxstack 2
 4     .locals init (
 5         [0int32 index,
 6         [1bool CS$4$0000)
 7     L_0000: nop 
 8     L_0001: ldc.i4.0 推入0
 9     L_0002: stloc.0 存到index
10     L_0003: br.s L_0009无条件跳转到L_0009
11     L_0005: ldloc.0 推入index
12     L_0006: ldc.i4.1 推入1
13     L_0007: add index+1
14     L_0008: stloc.0 存回index
15     L_0009: ldloc.0推入index
16     L_000a: ldsfld int32[] Class1::foo 推入数组foo
17     L_000f: ldlen推入数组的长度
18     L_0010: conv.i4 转换为Int32
19          L_0011: clt比较index和当前栈顶数组的长度;如果没有越界,则将1推向栈顶,反之推0
20     L_0013: stloc.1 结果存入CS$4$0000
21     L_0014: ldloc.1 推入CS$4$0000
22     L_0015: brtrue.s L_0005 CS$4$0000为真则跳转到L_0005
23     L_0017: ret 
24 }
25 

 

汇编码:

 

 1 for (int index = 0; index < foo.Length; index++)
 2 00000035  xor         edx,edx推入0
 3 00000037  mov         dword ptr [ebp-3Ch],edx存到index
 4 0000003a  nop              
 5 0000003b  jmp         00000040无条件跳转到L_0009
 6 0000003d  inc         dword ptr [ebp-3Ch] 
 7 00000040  mov         eax,dword ptr [ebp-3Ch] 推入index
 8 00000043  mov         edx,dword ptr ds:[02DA84C0h]  推入数组foo
 9 00000049  cmp         eax,dword ptr [edx+4]  比较index和当前数组的长度
10 0000004c  setl        al   如果没有越界,则将1推向栈顶,反之推0
11 0000004f  movzx       eax,al 
12 00000052  mov         dword ptr [ebp-40h],eax 将比较结果存入CS$4$0000
13 00000055  cmp         dword ptr [ebp-40h],0将CS$4$0000推向栈顶,如果CS$4$0000为true、非空或非零,则跳转到L_0005
14 
15 00000059  jne         0000003D 
16     70:             ;
17     71:     }
18 

 

for循环生成的代码则比较简单,而且只是检查越界一次。

第三个循环则和第二个没什么区别,就是多了一个临时变量,将IL和汇编码放在一起:

 

 1 .method private hidebysig static void Foo() cil managed
 2 {
 3     .maxstack 2
 4     .locals init (
 5         [0] int32 len,
 6         [1] int32 index,
 7         [2] bool CS$4$0000)
 8     L_0000: nop 
 9     L_0001: ldsfld int32[] Class1::foo
10     L_0006: ldlen 
11     L_0007: conv.i4 
12     L_0008: stloc.0 
13     L_0009: ldc.i4.0 
14     L_000a: stloc.1 
15     L_000b: br.s L_0011
16     L_000d: ldloc.1 
17     L_000e: ldc.i4.1 
18     L_000f: add 
19     L_0010: stloc.1 
20     L_0011: ldloc.1 
21     L_0012: ldloc.0 
22     L_0013: clt 
23     L_0015: stloc.2 
24     L_0016: ldloc.2 
25     L_0017: brtrue.s L_000d
26     L_0019: ret 
27 }
28 
29 69:         int len = foo.Length;
30 0000003a  mov         eax,dword ptr ds:[02C084C0h] 
31 0000003f  mov         eax,dword ptr [eax+4
32 00000042  mov         dword ptr [ebp-3Ch],eax 
33     70:         for (int index = 0; index < len; index++)
34 00000045  xor         edx,edx 
35 00000047  mov         dword ptr [ebp-40h],edx 
36 0000004a  nop              
37 0000004b  jmp         00000050 
38 0000004d  inc         dword ptr [ebp-40h] 
39 00000050  mov         eax,dword ptr [ebp-40h] 
40 00000053  cmp         eax,dword ptr [ebp-3Ch] 
41 00000056  setl        al   
42 00000059  movzx       eax,al 
43 0000005c  mov         dword ptr [ebp-44h],eax 
44 0000005f  cmp         dword ptr [ebp-44h],0 
45 00000063  jne         0000004D 
46     71:             ;
47 

 

我是在.Net3.5上运行的代码,没有实际测试1.0环境,也确实找不到了,如果错误之处,请大家指正。

PS:偶然的机会发现数组的长度不是计算的,而是在数组的首地址+4的位置存储的,大家可以用windbg看一看。

posted @ 2009-06-20 21:40  DiggingDeeply  阅读(2047)  评论(14编辑  收藏  举报