嗯,你计时的方式在我看来很糟糕。只计时整个循环会更明智:
var stopwatch = Stopwatch.StartNew();
for (int i = 1; i < 100000000; i++)
{
Fibo(100);
}
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}", stopwatch.Elapsed);
这样,您就不会受到微小计时、浮点运算和累积错误的影响。
进行了更改后,看看“非捕获”版本是否仍比“捕获”版本慢。
编辑:好吧,我自己也试过了,我也看到了同样的结果。非常奇怪。我想知道try/catch是否禁用了一些错误的内联,但改用[MethodImpl(MethodImplOptions.NoInlining)]没有帮助。。。
基本上,您需要查看cordbg下的优化JITted代码,我怀疑。。。
编辑:更多信息:
仅在n++周围进行尝试/捕获;这条线仍然提高了性能,但并没有把它放在整个街区如果你捕捉到一个特定的异常(我的测试中的ArgumentException),它仍然很快如果在catch块中打印异常,它仍然很快如果在catch块中重新抛出异常,那么它会再次变慢如果您使用finally块而不是catch块,那么速度会再次变慢如果你使用finally块和catch块,那么它很快
奇怪的
编辑:好的,我们有拆解。。。
这是使用C#编译器和.NET 2(32位)CLR,使用mdbg进行反汇编(因为我的机器上没有cordbg)。即使在调试器下,我仍然可以看到相同的性能效果。快速版本在变量声明和返回语句之间使用try块,只使用catch{}处理程序。显然,慢版本是相同的,除了没有try/catch。在这两种情况下,调用代码(即Main)都是相同的,并且具有相同的程序集表示(因此这不是内联问题)。
快速版本的分解代码:
[0000] push ebp
[0001] mov ebp,esp
[0003] push edi
[0004] push esi
[0005] push ebx
[0006] sub esp,1Ch
[0009] xor eax,eax
[000b] mov dword ptr [ebp-20h],eax
[000e] mov dword ptr [ebp-1Ch],eax
[0011] mov dword ptr [ebp-18h],eax
[0014] mov dword ptr [ebp-14h],eax
[0017] xor eax,eax
[0019] mov dword ptr [ebp-18h],eax
*[001c] mov esi,1
[0021] xor edi,edi
[0023] mov dword ptr [ebp-28h],1
[002a] mov dword ptr [ebp-24h],0
[0031] inc ecx
[0032] mov ebx,2
[0037] cmp ecx,2
[003a] jle 00000024
[003c] mov eax,esi
[003e] mov edx,edi
[0040] mov esi,dword ptr [ebp-28h]
[0043] mov edi,dword ptr [ebp-24h]
[0046] add eax,dword ptr [ebp-28h]
[0049] adc edx,dword ptr [ebp-24h]
[004c] mov dword ptr [ebp-28h],eax
[004f] mov dword ptr [ebp-24h],edx
[0052] inc ebx
[0053] cmp ebx,ecx
[0055] jl FFFFFFE7
[0057] jmp 00000007
[0059] call 64571ACB
[005e] mov eax,dword ptr [ebp-28h]
[0061] mov edx,dword ptr [ebp-24h]
[0064] lea esp,[ebp-0Ch]
[0067] pop ebx
[0068] pop esi
[0069] pop edi
[006a] pop ebp
[006b] ret
慢速版本的已分解代码:
[0000] push ebp
[0001] mov ebp,esp
[0003] push esi
[0004] sub esp,18h
*[0007] mov dword ptr [ebp-14h],1
[000e] mov dword ptr [ebp-10h],0
[0015] mov dword ptr [ebp-1Ch],1
[001c] mov dword ptr [ebp-18h],0
[0023] inc ecx
[0024] mov esi,2
[0029] cmp ecx,2
[002c] jle 00000031
[002e] mov eax,dword ptr [ebp-14h]
[0031] mov edx,dword ptr [ebp-10h]
[0034] mov dword ptr [ebp-0Ch],eax
[0037] mov dword ptr [ebp-8],edx
[003a] mov eax,dword ptr [ebp-1Ch]
[003d] mov edx,dword ptr [ebp-18h]
[0040] mov dword ptr [ebp-14h],eax
[0043] mov dword ptr [ebp-10h],edx
[0046] mov eax,dword ptr [ebp-0Ch]
[0049] mov edx,dword ptr [ebp-8]
[004c] add eax,dword ptr [ebp-1Ch]
[004f] adc edx,dword ptr [ebp-18h]
[0052] mov dword ptr [ebp-1Ch],eax
[0055] mov dword ptr [ebp-18h],edx
[0058] inc esi
[0059] cmp esi,ecx
[005b] jl FFFFFFD3
[005d] mov eax,dword ptr [ebp-1Ch]
[0060] mov edx,dword ptr [ebp-18h]
[0063] lea esp,[ebp-4]
[0066] pop esi
[0067] pop ebp
[0068] ret
在每种情况下,*都显示调试器在简单的“单步执行”中输入的位置。
编辑:好的,我现在已经看过了代码,我想我可以看到每个版本是如何工作的。。。我认为较慢的版本较慢,因为它使用更少的寄存器和更多的堆栈空间。对于较小的n值,这可能更快,但当循环占用大部分时间时,它会更慢。
可能try/catch块会强制保存和恢复更多的寄存器,所以JIT也会在循环中使用这些寄存器。。。这恰好提高了整体性能。目前还不清楚JIT不在“正常”代码中使用那么多寄存器是否合理。
编辑:刚刚在我的x64计算机上尝试过。在这段代码中,x64 CLR比x86 CLR快得多(大约快3-4倍),在x64下,try/catch块没有明显的区别。