奇怪的表现行为

Tam*_*red 15 c# performance cil

所以我有这两种方法,假设将1000项长整数数组乘以2.第一种方法:

[MethodImpl(MethodImplOptions.NoOptimization)]
Power(int[] arr)
{
    for (int i = 0; i < arr.Length; i++)
    {
        arr[i] = arr[i] + arr[i];
    }
}
Run Code Online (Sandbox Code Playgroud)

第二种方法:

[MethodImpl(MethodImplOptions.NoOptimization)]
PowerNoLoop(int[] arr)
{
    int i = 0;
    arr[i] = arr[i] + arr[i];
    i++;
    arr[i] = arr[i] + arr[i];
    i++;
    arr[i] = arr[i] + arr[i];
    i++;
    ............1000 Times........
    arr[i] = arr[i] + arr[i];
}
Run Code Online (Sandbox Code Playgroud)

请注意,我仅将此代码用于性能研究,这就是为什么它看起来如此恶心.

令人惊讶的结果是,Power比PowerNoLoop我们检查了IL两者的反编译源并且for循环的内容与每行完全相同的速度快了近50%PowerNoLoop.怎么会这样?

Han*_*ant 12

我的机器上的样品测量,运行测试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
Run Code Online (Sandbox Code Playgroud)

是的,慢了约50%.值得注意的是第一次通过测试时的抖动开销,显然它会烧掉更多核心,试图获得编译的巨大方法.请记住,当您不禁用优化程序时,测量会有很大差异,因此无循环版本会慢约800%.

始终寻找解释的第一个地方是生成的机器代码,您可以使用Debug> Windows> Disassembly查看它.主要的麻烦是该PowerNoLoop()方法的序幕.在x86代码中看起来像这样:

067E0048  push        ebp                       ; setup stack frame
067E0049  mov         ebp,esp  
067E004B  push        edi                       ; preserve registers
067E004C  push        esi  
067E004D  sub         esp,0FA8h                 ; stack frame size = 4008 bytes  
067E0053  mov         esi,ecx  
067E0055  lea         edi,[ebp-0ACCh]           ; temp2 variables
067E005B  mov         ecx,2B1h                  ; initialize 2756 bytes
067E0060  xor         eax,eax                   ; set them to 0
067E0062  rep stos    dword ptr es:[edi] 
Run Code Online (Sandbox Code Playgroud)

注意非常大的堆栈大小,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
Run Code Online (Sandbox Code Playgroud)

一遍又一遍地重复,总共一千次.该temp2变量是麻烦制造者,还有每一个为每个单独的语句.因此向堆栈帧大小添加4000个字节.如果有人在2756猜测,那么我很乐意在评论中听到它.

在方法开始运行之前必须将它们全部设置为0大致是产生50%减速的原因.可能还有一些指令获取和解码开销,它不能轻易地从测量中分离出来.

值得注意的是,当您删除[MethodImpl]属性并允许优化器完成其工作时,它们不会被消除.事实上,该方法根本没有优化,当然因为它不想处理如此大的代码块.


您应该绘制的结论是始终将其留给抖动优化器来为您展开循环.它知道的更好.