我从我的机器上进行了一项测试,测试运行了10次,PowerNoLoop
是第一个:
00:00:00.0277138 00:00:00.0001553
00:00:00.0000142 00:00:00.0000057
00:00:00.0000106 00:00:00.0000053
00:00:00.0000084 00:00:00.0000053
00:00:00.0000080 00:00:00.0000053
00:00:00.0000075 00:00:00.0000053
00:00:00.0000080 00:00:00.0000057
00:00:00.0000080 00:00:00.0000053
00:00:00.0000080 00:00:00.0000053
00:00:00.0000075 00:00:00.0000053
是的,大约慢50%。显然第一次通过测试时存在抖动开销,因为尝试编译巨大的方法会占用更多的核心。请记住,当不禁用优化器时,测量结果会有很大差异,无循环版本则慢了约800%。
总是要查找解释的第一位置是生成的机器代码,可以在“调试>窗口>反汇编”中看到它。主要问题出现在PowerNoLoop()
方法的序言中。在x86代码中看起来像这样:
067E0048 push ebp
067E0049 mov ebp,esp
067E004B push edi
067E004C push esi
067E004D sub esp,0FA8h
067E0053 mov esi,ecx
067E0055 lea edi,[ebp-0ACCh]
067E005B mov ecx,2B1h
067E0060 xor eax,eax
067E0062 rep stos dword ptr es:[edi]
请注意栈大小非常大,为4008个字节。对于只有一个本地变量的方法来说,这太多了,它只需要8个字节。额外的4000个字节是临时变量,我将它们命名为temp2
。它们由rep stos
指令初始化为0,这需要一些时间。我解释不了2756。
在未经优化的代码中,单个加法操作非常缓慢。我将避免写出机器码转储,并用等效的C#代码代替:
if (i >= arr.Length) goto throwOutOfBoundsException
var temp1 = arr[i];
if (i >= arr.Length) goto throwOutOfBoundsException
var temp2 = temp1 + arr[i];
if (i >= arr.Length) goto throwOutOfBoundsException
arr[i] = temp2
重复了一千次,每个语句都有一个temp2
变量,它是个麻烦制造者,所以为栈帧增加了4000字节的大小。如果有人对2756有猜想,我很想在评论中听到。
在方法开始运行之前,必须将它们全部设置为0,这大致上会导致50%的减速。可能还有一些指令获取和解码开销,很难从测量中单独分离出来。
值得注意的是,当您移除[MethodImpl]属性并允许优化器去做它的工作时,它们并没有被消除。实际上,该方法根本没有经过优化,肯定是因为它不想处理这么大的代码块。
你应该得出的结论是:始终将循环展开的任务留给JIT优化器完成,它知道得更好。
arg[i] <<= 1
的速度更快。 - Richard Schneider