[英]foreach vs for: please explain the assembly code difference
我最近一直在測試for循環與C#中的foreach循環的性能,我注意到,為了將一個整數的數組相加成一個long,foreach循環可能實際上更快。 這是完整的測試程序 ,我使用了Visual Studio 2012,x86,發布模式,優化。
這是兩個循環的匯編代碼。 foreach:
long sum = 0;
00000000 push ebp
00000001 mov ebp,esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 xor ebx,ebx
00000008 xor edi,edi
foreach (var i in collection) {
0000000a xor esi,esi
0000000c cmp dword ptr [ecx+4],0
00000010 jle 00000025
00000012 mov eax,dword ptr [ecx+esi*4+8]
sum += i;
00000016 mov edx,eax
00000018 sar edx,1Fh
0000001b add ebx,eax
0000001d adc edi,edx
0000001f inc esi
foreach (var i in collection) {
00000020 cmp dword ptr [ecx+4],esi
00000023 jg 00000012
}
return sum;
00000025 mov eax,ebx
00000027 mov edx,edi
00000029 pop ebx
0000002a pop esi
0000002b pop edi
0000002c pop ebp
0000002d ret
並為:
long sum = 0;
00000000 push ebp
00000001 mov ebp,esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 push eax
00000007 xor ebx,ebx
00000009 xor edi,edi
for (int i = 0; i < collection.Length; ++i) {
0000000b xor esi,esi
0000000d mov eax,dword ptr [ecx+4]
00000010 mov dword ptr [ebp-10h],eax
00000013 test eax,eax
00000015 jle 0000002A
sum += collection[i];
00000017 mov eax,dword ptr [ecx+esi*4+8]
0000001b cdq
0000001c add eax,ebx
0000001e adc edx,edi
00000020 mov ebx,eax
00000022 mov edi,edx
for (int i = 0; i < collection.Length; ++i) {
00000024 inc esi
00000025 cmp dword ptr [ebp-10h],esi
00000028 jg 00000017
}
return sum;
0000002a mov eax,ebx
0000002c mov edx,edi
0000002e pop ecx
0000002f pop ebx
00000030 pop esi
00000031 pop edi
00000032 pop ebp
00000033 ret
如您所見,主循環是7個“foreach”指令和9個“for”指令。 這轉化為我的基准測試中大約10%的性能差異。
我不是很擅長閱讀匯編代碼但是我不明白為什么for循環不會像foreach那樣有效。 這里發生了什么?
由於數組太大,唯一的相關部分顯然是循環中的一個,這個:
// for loop
00000017 mov eax,dword ptr [ecx+esi*4+8]
0000001b cdq
0000001c add eax,ebx
0000001e adc edx,edi
00000020 mov ebx,eax
00000022 mov edi,edx
// foreach loop
00000012 mov eax,dword ptr [ecx+esi*4+8]
00000016 mov edx,eax
00000018 sar edx,1Fh
0000001b add ebx,eax
0000001d adc edi,edx
由於和是一個long int,它存儲在兩個不同的寄存器中,即ebx包含其最不重要的四個字節,edi包含最重要的四個字節。 它們在集合[i](隱式)從int轉換為long方面有所不同:
// for loop
0000001b cdq
// foreach loop
00000016 mov edx,eax
00000018 sar edx,1Fh
需要注意的另一個重要事項是for循環版本以“反向”順序執行求和:
long temp = (long) collection[i]; // implicit cast, stored in edx:eax
temp += sum; // instead of "simply" sum += temp
sum = temp; // sum is stored back into ebx:edi
我不能告訴你為什么編譯器首選這種方式而不是sum + = temp(@EricLippert可能告訴我們:))但我懷疑它與可能出現的一些指令依賴性問題有關。
好的,所以這是一個帶注釋的匯編代碼版本,因為你會看到循環中的指令非常接近。
foreach (var i in collection) {
0000000a xor esi,esi clear index
0000000c cmp dword ptr [ecx+4],0 get size of collection
00000010 jle 00000025 exit if empty
00000012 mov eax,dword ptr [ecx+esi*4+8] get item from collection
sum += i;
00000016 mov edx,eax move to edx:eax
00000018 sar edx,1Fh shift 31 bits to keep sign only
0000001b add ebx,eax add to sum
0000001d adc edi,edx add with carry from previous add
0000001f inc esi increment index
foreach (var i in collection) {
00000020 cmp dword ptr [ecx+4],esi compare size to index
00000023 jg 00000012 loop if more
}
return sum;
00000025 mov eax,ebx result was in ebx
=================================================
for (int i = 0; i < collection.Length; ++i) {
0000000b xor esi,esi clear index
0000000d mov eax,dword ptr [ecx+4] get limit on for
00000010 mov dword ptr [ebp-10h],eax save limit
00000013 test eax,eax test if limit is empty
00000015 jle 0000002A exit loop if empty
sum += collection[i];
00000017 mov eax,dword ptr [ecx+esi*4+8] get item form collection
0000001b cdq convert eax to edx:eax
0000001c add eax,ebx add to sum
0000001e adc edx,edi add with carry from previous add
00000020 mov ebx,eax put result in edi:ebx
00000022 mov edi,edx
for (int i = 0; i < collection.Length; ++i) {
00000024 inc esi increment index
00000025 cmp dword ptr [ebp-10h],esi compare to limit
00000028 jg 00000017 loop if more
}
return sum;
0000002a mov eax,ebx result was in ebx
根據C#語言規范4.0 ,編譯器將foreach
循環分解為以下內容:
foreach-statement :
foreach( 表達式中的 local-variable-type 標識符 ) embedded-statement
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
這是在以下處理之后(再次來自規范):
• 如果表達式的 X類型是數組類型,則存在從X到
System.Collections.IEnumerable
接口的隱式引用轉換 (因為System.Array
實現此接口)。 集合類型是System.Collections.IEnumerable
接口,枚舉器類型是System.Collections.IEnumerator
接口,元素類型是數組類型X的元素類型。
可能是您沒有從編譯器中看到相同匯編代碼的一個很好的理由。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.