さて、あなたが物事を計っている方法は私にはかなり厄介に見えます。ループ全体の時間を計測するほうがはるかに賢明です。
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で最適化されたJITされたコードを確認する必要があると思います...
編集:情報のいくつかのビット:
n++;
行のみにtry / catchを配置しても、パフォーマンスは向上しますが、ブロック全体に配置するほどではありません
- (
ArgumentException
私のテストで)特定の例外をキャッチした場合、それはまだ高速です
- あなたがキャッチブロックで例外を出力するならば、それはまだ速いです
- キャッチブロックで例外を再スローすると、再び遅くなります
- catchブロックの代わりにfinallyブロックを使用すると、再び遅くなります
- finallyブロックと catchブロックを使用すると、高速です
奇妙な...
編集:さて、私たちは分解しています...
これはC#2コンパイラと.NET 2(32ビット)CLRを使用しており、mdbgを使用して逆アセンブルします(マシンにcordbgがないため)。デバッガーでも、同じパフォーマンス効果が表示されます。高速バージョンではtry
、catch{}
ハンドラーのみで、変数宣言とreturnステートメントの間のすべてを囲むブロックを使用します。明らかに遅いバージョンは、try / catchがないことを除いて同じです。呼び出しコード(メインなど)はどちらの場合も同じで、アセンブリ表現も同じです(インライン化の問題ではありません)。
高速バージョンの逆アセンブルされたコード:
[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ブロックは顕著な違いを生じません。