导航

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

Posted on 2008-08-21 22:30  BitsBlue  阅读(2873)  评论(10)    收藏  举报

说明一下,本人都是利用工作后的业余时间无条件的为cnblogs写些技术心得.不管写的好还是不好,多还是少都只是希望更多的人能够收益,同时通过分析来提高自己的理解.希望大家能够相互尊重彼此的劳动.


另外我也理解汇编代码可能对大部分人来说太复杂也觉得不实用. 确实,如果只是停留在基础编程的话,可能这是多余的,但是如果希望自己能够深入理解clr的话,这可能又是必须的.


闲话少说,来看看ForeachTest是怎么工作的. (注意黄色标识出来的代码)


首先通过!name2ee test Test.Program.ForeachTest找到编译后的地址,然后!u反编译.具体过程可以参考上一篇.

 具体代码和注释如下:

>>> 00e30138 55              push    ebp
00e30139 8bec            mov     ebp,esp
00e3013b 57              push    edi
00e3013c 56              push    esi
00e3013d 53              push    ebx
00e3013e 83ec18          sub     esp,18h
00e30141 33c0            xor     eax,eax //eax 清零
00e30143 8945e0          mov     dword ptr [ebp-20h],eax //置零
00e30146 8945e4          mov     dword ptr [ebp-1Ch],eax //置零
00e30149 8945e8          mov     dword ptr [ebp-18h],eax //置零
00e3014c 8945ec          mov     dword ptr [ebp-14h],eax //置零
00e3014f 33c0            xor     eax,eax //eax 清零
00e30151 8945e8          mov     dword ptr [ebp-18h],eax //置零
00e30154 8bf1            mov     esi,ecx //Samples集合对象地址放入esi
00e30156 3906            cmp     dword ptr [esi],eax
00e30158 33d2            xor     edx,edx //edx置零
00e3015a 8b4610          mov     eax,dword ptr [esi+10h] //保存Samples集合对象的_version进eax,这里_version是3
00e3015d 8945dc          mov     dword ptr [ebp-24h],eax //再把_version存入指定内存地址
00e30160 8bda            mov     ebx,edx //ebx置零
00e30162 eb14            jmp     00e30178 //无条件跳转到 00e30178
00e30164 8b7a04          mov     edi,dword ptr [edx+4] //取出Sample对象的Value并存入edit,这里也可以说是循环开始处
*** WARNING: Unable to verify checksum for C:"Windows"assembly"NativeImages_v2.0.50727_32"mscorlib"9adb89fa22fd5b4ce433b5aca7fb1b07"mscorlib.ni.dll
00e30167 e834d1b966      call    mscorlib_ni+0x22d2a0 (679cd2a0) (System.Console.get_Out(), mdToken: 06000772) //获取Console.Out
00e3016c 8bc8            mov     ecx,eax //保存Console.Out到ecx
00e3016e 8bd7            mov     edx,edi //保存edi到edx
00e30170 8b01            mov     eax,dword ptr [ecx] //保存Console.Out的method table到eax
00e30172 ff90bc000000    call    dword ptr [eax+0BCh] //打印Value值到屏幕
00e30178 8b45dc          mov     eax,dword ptr [ebp-24h] //读出_version到eax
00e3017b 3b4610          cmp     eax,dword ptr [esi+10h] //比较_version与指定内存地址
00e3017e 7519            jne     00e30199 //不相等就跳转到00e30199
00e30180 3b5e0c          cmp     ebx,dword ptr [esi+0Ch] //比较ebx与Samples集合的_size
00e30183 7314            jae     00e30199 //大于等于就跳转到 00e30199
00e30185 8b4604          mov     eax,dword ptr [esi+4] //保存Sample集合对象中的_items对象地址到eax(_items就是一个object数组内存用来存储List集合中的数据)
00e30188 3b5804          cmp     ebx,dword ptr [eax+4] //从内存中获取_items的元素个数并与ebx比较
00e3018b 733f            jae     00e301cc //大于等于就跳转到JIT_RngChkFail
00e3018d 8b54980c        mov     edx,dword ptr [eax+ebx*4+0Ch] //以ebx中的值为索引获取一个Sample对象并存入edx
00e30191 43              inc     ebx //ebx加1
00e30192 b801000000      mov     eax,1 //eax置1
00e30197 eb1a            jmp     00e301b3 //无条件跳转到00e301b3,就是下面test指令处
00e30199 8b45dc          mov     eax,dword ptr [ebp-24h] //读出_version到eax
00e3019c 3b4610          cmp     eax,dword ptr [esi+10h] //比较_version与指定内存地址
00e3019f 740a            je      00e301ab //相等就跳转到00e301ab
00e301a1 b920000000      mov     ecx,20h
00e301a6 e8e1abff66      call    mscorlib_ni+0x68ad8c (67e2ad8c) (System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource), mdToken: 060000e2)
00e301ab 8b5e0c          mov     ebx,dword ptr [esi+0Ch] //读出Sample对象的_size,应该为3
00e301ae 43              inc     ebx //ebx加1
00e301af 33d2            xor     edx,edx //edx置零
00e301b1 33c0            xor     eax,eax //eax置零
00e301b3 85c0            test    eax,eax //循环没有结束的时候eax总是1,如果循环要结束了eax是0
00e301b5 75ad            jne     00e30164 //eax是1时跳到循环开始处
00e301b7 c745e400000000  mov     dword ptr [ebp-1Ch],0
00e301be c745e8fc000000  mov     dword ptr [ebp-18h],0FCh
00e301c5 68dc01e300      push    0E301DCh
00e301ca eb05            jmp     00e301d1
00e301cc e8ebbe1c68      call    mscorwks!JIT_RngChkFail (68ffc0bc)
00e301d1 58              pop     eax
00e301d2 ffe0            jmp     eax
00e301d4 8d65f4          lea     esp,[ebp-0Ch]
00e301d7 5b              pop     ebx
00e301d8 5e              pop     esi
00e301d9 5f              pop     edi
00e301da 5d              pop     ebp
00e301db c3              ret

 

 仔细比较Fortest和ForeachTest,你会发现区别所在.

1. ForTest使用edi 做为累加器和List的_size比较, 比较结果做为是循环是否继续的决定条件. ForeachTest在进入循环以前会先保存List的_version并在循环中比较. _version和_size两个是决定是否继续循环的条件.

2. List中的_version和_size 的值是一样的. 如果在循环过程中List中的值添加或减少, 新的_version就会和老的(循环进入前保存)的_version不一样,从而导致InvalidOperationException抛出.

3. 因为ForeachTest比ForTest多了一个判断条件,所以理论上ForeachTest的性能应该低于ForTest. (注意这里是说低,但低多少要根据实际情况做测试)

 

经过笔者的实际测试,分别对两个循环连续运行1,000,000次在一台奔4有3g内存的机器上, ForTest比ForeachTest快100多毫秒. 

 

到底是for 还是 foreach呢?

笔者认为虽然for可以比foreach带来微小的性能提升,但是foreach的代码更容易理解.对于大部分程序来说,这点性能提升是十分微不足道的. 另外要意识到foreach操作的集合类型中,循环中添加,删除都是不允许的, 但是for在这方面更容易控制.

 

最后送给大家一段话来自微软的Chief Architect of Visual Studio, Rico Mariani, 说的基本规则关于performance.

Rule #1: Measure
Rule #2: Do Your Homework