Eh bien, la façon dont vous chronométrez les choses me semble assez désagréable. Il serait beaucoup plus judicieux de simplement chronométrer toute 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 minuscules synchronisations, d'arithmétique à virgule flottante et d'erreur accumulée.
Après avoir fait ce changement, voyez si la version "non-catch" est encore plus lente que la version "catch".
EDIT: D'accord, je l'ai essayé moi-même - et je vois le même résultat. Très étrange. Je me demandais si le try / catch désactivait une mauvaise incrustation, mais l'utilisation à la [MethodImpl(MethodImplOptions.NoInlining)]
place n'a pas aidé ...
Fondamentalement, vous devrez regarder le code JITted optimisé sous cordbg, je suppose ...
EDIT: Quelques informations supplémentaires:
- Mettre le try / catch autour de la
n++;
ligne améliore toujours les performances, mais pas autant que de le faire autour du bloc entier
- Si vous attrapez une exception spécifique (
ArgumentException
dans mes tests) c'est toujours rapide
- Si vous imprimez l'exception dans le bloc catch, c'est toujours rapide
- Si vous relancez l'exception dans le bloc catch, c'est à nouveau lent
- Si vous utilisez un bloc finally au lieu d'un bloc catch, c'est à nouveau lent
- Si vous utilisez un bloc finally ainsi qu'un bloc catch, c'est rapide
Bizarre...
EDIT: D'accord, nous avons le démontage ...
Cela utilise le compilateur C # 2 et le CLR .NET 2 (32 bits), en se désassemblant avec mdbg (car je n'ai pas cordbg sur ma machine). Je vois toujours les mêmes effets de performance, même sous le débogueur. La version rapide utilise un try
bloc autour de tout entre les déclarations de variables et l'instruction de retour, avec juste un catch{}
gestionnaire. Évidemment, la version lente est la même, sauf sans le try / catch. Le code appelant (c.-à-d. Principal) est le même dans les deux cas, et a la même représentation d'assembly (donc ce n'est pas un problème en ligne).
Code démonté pour une 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émonté 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, le *
montre où le débogueur est entré dans un simple "step-into".
EDIT: D'accord, j'ai maintenant parcouru le code et je pense que je peux voir comment chaque version fonctionne ... et je crois que la version plus lente est plus lente car elle utilise moins de registres et plus d'espace de pile. Pour les petites valeurs de n
c'est peut-être plus rapide - mais lorsque la boucle prend la majeure partie du temps, elle est plus lente.
Peut-être que le bloc try / catch force plus de registres à être sauvegardés et restaurés, donc le JIT utilise aussi ceux pour la boucle ... ce qui s'avère améliorer les performances globales. Il n'est pas clair si c'est une décision raisonnable pour le JIT de ne pas utiliser autant de registres dans le code "normal".
EDIT: Je viens d'essayer cela sur ma machine x64. Le CLR x64 est beaucoup plus rapide (environ 3-4 fois plus rapide) que le CLR x86 sur ce code, et sous x64, le bloc try / catch ne fait pas de différence notable.