我最近在C_中测试了for循环和foreach循环的性能,我注意到将一个整数数组求和成一个长整数数组时,foreach循环实际上可能会更快。Here is the full test program,我使用了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

由于和是一个长整数,所以它存储在两个不同的寄存器中,即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可能会告诉我们:)),但我怀疑这与可能出现的一些指令依赖性问题有关。

07-24 09:44
查看更多