Nun, die Art und Weise, wie Sie die Dinge planen, sieht für mich ziemlich böse aus. Es wäre viel sinnvoller, nur die gesamte Schleife zu messen:
var stopwatch = Stopwatch.StartNew();
for (int i = 1; i < 100000000; i++)
{
Fibo(100);
}
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}", stopwatch.Elapsed);
Auf diese Weise sind Sie nicht winzigen Timings, Gleitkomma-Arithmetik und akkumulierten Fehlern ausgeliefert.
Nachdem Sie diese Änderung vorgenommen haben, prüfen Sie, ob die "non-catch" -Version noch langsamer als die "catch" -Version ist.
EDIT: Okay, ich habe es selbst versucht - und ich sehe das gleiche Ergebnis. Sehr komisch. Ich fragte mich, ob der Versuch / Fang ein schlechtes Inlining deaktivierte, aber [MethodImpl(MethodImplOptions.NoInlining)]
stattdessen half es nicht ...
Grundsätzlich müssen Sie sich den optimierten JITted-Code unter cordbg ansehen, vermute ich ...
EDIT: Noch ein paar Informationen:
- Wenn Sie den Versuch / Fang nur um die
n++;
Linie legen, wird die Leistung immer noch verbessert, jedoch nicht so sehr wie um den gesamten Block
- Wenn Sie eine bestimmte Ausnahme (
ArgumentException
in meinen Tests) feststellen, ist diese immer noch schnell
- Wenn Sie die Ausnahme im catch-Block drucken, ist sie immer noch schnell
- Wenn Sie die Ausnahme im catch-Block erneut auslösen, ist sie wieder langsam
- Wenn Sie einen finally-Block anstelle eines catch-Blocks verwenden, ist dieser wieder langsam
- Wenn Sie sowohl einen finally-Block als auch einen catch-Block verwenden, ist dies schnell
Seltsam...
EDIT: Okay, wir haben Demontage ...
Dies verwendet den C # 2-Compiler und .NET 2 (32-Bit) CLR, die mit mdbg zerlegt werden (da ich kein Cordbg auf meinem Computer habe). Ich sehe immer noch die gleichen Leistungseffekte, auch unter dem Debugger. Die schnelle Version verwendet einen try
Block um alles zwischen den Variablendeklarationen und der return-Anweisung mit nur einem catch{}
Handler. Offensichtlich ist die langsame Version dieselbe, außer ohne try / catch. Der aufrufende Code (dh Main) ist in beiden Fällen derselbe und hat dieselbe Assembly-Darstellung (es handelt sich also nicht um ein Inlining-Problem).
Zerlegter Code für schnelle Version:
[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
Zerlegter Code für langsame Version:
[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
In jedem Fall *
zeigt das , wo der Debugger in einem einfachen "Step-In" eingegeben hat.
EDIT: Okay, ich habe jetzt den Code durchgesehen und ich denke, ich kann sehen, wie jede Version funktioniert ... und ich glaube, die langsamere Version ist langsamer, weil sie weniger Register und mehr Stapelspeicher verwendet. Für kleine Werte ist n
das möglicherweise schneller - aber wenn die Schleife den größten Teil der Zeit in Anspruch nimmt, ist sie langsamer.
Möglicherweise erzwingt der Try / Catch-Block , dass mehr Register gespeichert und wiederhergestellt werden, sodass die JIT diese auch für die Schleife verwendet ... was die Leistung insgesamt verbessert. Es ist nicht klar, ob es eine vernünftige Entscheidung für die JIT ist, nicht so viele Register im "normalen" Code zu verwenden.
EDIT: Hab das gerade auf meinem x64 Rechner ausprobiert. Die x64-CLR ist viel schneller (ungefähr 3-4 mal schneller) als die x86-CLR in diesem Code, und unter x64 macht der try / catch-Block keinen merklichen Unterschied.