簡體   English   中英

foreach vs for:請解釋匯編代碼差異

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM