深入了解 foreach 和 for 循环到底有哪些不同 (二)
2008-08-21 22:30
691 查看
说明一下,本人都是利用工作后的业余时间无条件的为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
另外我也理解汇编代码可能对大部分人来说太复杂也觉得不实用. 确实,如果只是停留在基础编程的话,可能这是多余的,但是如果希望自己能够深入理解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
相关文章推荐
- 深入了解 foreach 和 for 循环到底有哪些不同 (一)
- 深入了解 JavaScript 中的 for 循环
- 深入了解 JavaScript 中的 for 循环
- 深入了解java8的foreach循环
- 深入 foreach 和 for 循环的区别
- 深入理解java中for和foreach循环
- 深入了解 JavaScript 中的 for 循环
- 深入了解 JavaScript 中的 for 循环
- 深入了解 JavaScript 中的 for 循环
- 深入 foreach 和 for 循环的区别
- 深入了解 JavaScript 中的 for 循环 – 码农网
- 深入了解 JavaScript 中的 for 循环
- php循环语句 for()与foreach()用法区别介绍
- for ,foreach ,map 循环的区别
- Java for循环和foreach循环的性能比较
- 深入了解Windows句柄到底是什么
- 深入for,while,foreach遍历时间比较的详解
- Java中的增强 for 循环 foreach+移除元素
- JAVA foreach和普通for循环是否需要判断为null