Eh bien, la façon dont vous êtes timing choses a l'air assez méchant pour moi. Il serait beaucoup plus judicieux de juste le temps la totalité de la boucle:
var stopwatch = Stopwatch.StartNew();
for (int i = 1; i < 100000000; i++)
{
Fibo(100);
}
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}", stopwatch.Elapsed);
De cette façon, vous n'êtes pas à la merci de petits horaires, arithmétique à virgule flottante et le cumul des erreurs.
Après avoir apporté cette modification, voir si le "non-catch" version est encore plus lente que la "prise" version.
EDIT: Bon, j'ai essayé moi-même - et je vois le même résultat. Très bizarre. Je me demandais si le try/catch était la désactivation de certains mal l'in-lining, mais à l'aide de [MethodImpl(MethodImplOptions.NoInlining)]
au lieu de cela n'aide pas...
Fondamentalement, vous aurez besoin de regarder à l'optimisation de la JITted code sous cordbg, je le soupçonne...
EDIT: UN peu plus de bits d'information:
- Mettre le try/catch autour de l'
n++;
ligne est encore améliore les performances, mais pas comme de la mettre autour du bloc
- Si vous attrapez une exception spécifique (
ArgumentException
dans mes tests), il est encore rapide
- Si vous imprimez à l'exception dans le bloc catch, c'est toujours rapide
- Si vous renvoyer l'exception dans le bloc catch c'est lent à nouveau
- Si vous utilisez un bloc finally au lieu d'un bloc catch c'est lent à nouveau
- Si vous utilisez un bloc finally ainsi que d' un bloc catch, c'est rapide
Bizarre...
EDIT: Bon, nous avons démontage...
C'est à l'aide de C# 2 et d'un compilateur .NET 2 (32 bits) CLR, le démontage avec mdbg (que je n'ai pas cordbg sur ma machine). Je vois toujours les mêmes effets sur les performances, même dans le débogueur. La version rapide utilise un try
bloc autour de tout ce qui entre les déclarations de variables et de l'instruction de retour, avec juste un catch{}
gestionnaire. De toute évidence la lenteur de la version est la même mais sans le try/catch. Le code d'appel (c'est à dire le Principal) est le même dans les deux cas, et a la même représentation de l'assemblée (il n'est donc pas un inline question).
Code désassemblé pour la version rapide:
[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
Code désassemblé pour la version lente:
[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
Dans chaque cas, l' *
montre où le débogueur est entré dans un simple "étape".
EDIT: Bon, j'ai regardé le code et je pense que je peux voir comment chaque version fonctionne... et je crois que la version la plus lente est plus lent, car il utilise moins de registres et de plus d'espace de pile. Pour de petites valeurs de n
c'est peut-être plus rapidement - mais quand la boucle reprend l'essentiel du temps, c'est plus lent.
Éventuellement, le bloc try/catch forces plus registres sauvegardés et restaurés, de sorte que le JIT utilise ceux de la boucle... qui arrive à améliorer la performance globale. Il n'est pas clair si c'est une décision raisonnable pour l'équipe de ne pas utiliser autant de registres dans la "normale" du code.
EDIT: Juste essayé sur ma machine x64. Le x64 CLR est beaucoup plus rapide (environ 3 à 4 fois plus rapide) que le x86 CLR sur ce code, et sous x64 le bloc try/catch ne pas faire une différence notable.