Що ж, те, як ви приурочуєте речі, виглядає для мене досить противно. Було б набагато розумніше просто раз провести весь цикл:
var stopwatch = Stopwatch.StartNew();
for (int i = 1; i < 100000000; i++)
{
Fibo(100);
}
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}", stopwatch.Elapsed);
Таким чином, вам не до душі крихітні таймінги, арифметика з плаваючою комою та накопичена помилка.
Зробивши цю зміну, подивіться, чи все-таки повільніша версія "не лову", ніж версія "лову".
EDIT: Гаразд, я сам спробував це - і я бачу той же результат. Дуже дивно. Мені було цікаво, чи відключення спробу / лову відключило якийсь поганий вклад, але використовуючи[MethodImpl(MethodImplOptions.NoInlining)]
натомість не допомогло ...
В основному вам потрібно буде переглянути оптимізований код JITted під cordbg, я підозрюю ...
EDIT: Ще кілька біт інформації:
- Поставивши спробувати / наздогнати лише те
n++;
поставити лінію, все одно покращується продуктивність, але не настільки, як розміщення її по всьому блоку
- Якщо ви ловите конкретний виняток (
ArgumentException
у моїх тестах), це все одно швидко
- Якщо ви надрукуєте виняток у блоці вилову, це все ще швидко
- Якщо ви повторно скидаєте виняток у блок ловлі, це знову повільно
- Якщо ви використовуєте остаточно блок замість блоку лову, він знову повільний
- Якщо ви використовуєте остаточний блок , а також блок лову, це швидко
Дивно ...
EDIT: Гаразд, у нас є розбирання ...
Для цього використовується компілятор C # 2 та .NET 2 (32-розрядний) CLR, розбирання з mdbg (оскільки у мене на моїй машині не існує cordbg). Я все ще бачу ті ж ефекти, навіть під налагоджувачем. Швидка версія використовує try
блок навколо всього між деклараціями змінної та оператором return, за допомогою лише 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
У кожному випадку *
показує, куди налагоджувач увійшов у простий "крок".
EDIT: Гаразд, я зараз переглянув код і, думаю, я можу побачити, як працює кожна версія ... і я вважаю, що більш повільна версія повільніше, оскільки вона використовує менше регістрів і більше місця для стеку. Для невеликих значень n
це можливо швидше - але коли цикл займає основну частину часу, це відбувається повільніше.
Можливо, блок "try / catch" змушує більше реєстрів зберігатись і відновлюватися, тому JIT використовує і ці для циклу ... що відбувається для покращення загальної продуктивності. Не ясно, чи є розумним рішенням JIT не використовувати стільки регістрів у "звичайному" коді.
EDIT: Просто спробував це на моїй машині x64. CL64 x64 набагато швидше (приблизно в 3-4 рази швидше), ніж CL86 x86 у цьому коді, і під x64 блок try / catch не помітно помітний.