Şeyleri zamanlama şeklim bana çok kötü geliyor. Sadece tüm döngüyü zamanlamak çok daha mantıklı olurdu:
var stopwatch = Stopwatch.StartNew();
for (int i = 1; i < 100000000; i++)
{
Fibo(100);
}
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}", stopwatch.Elapsed);
Bu şekilde küçük zamanlamalar, kayan nokta aritmetiği ve biriken hataların merhametine kapılmıyorsunuz.
Bu değişikliği yaptıktan sonra, "yakalamayan" sürümün "yakalama" sürümünden hala daha yavaş olup olmadığına bakın.
EDIT: Tamam, kendim denedim - ve aynı sonucu görüyorum. Çok tuhaf. Ben denemek / yakalamak bazı kötü inlining devre dışı bırakıyor olup olmadığını merak, ama [MethodImpl(MethodImplOptions.NoInlining)]
bunun yerine kullanarak yardımcı olmadı ...
Temelde cordbg altında optimize JITted koduna bakmak gerekir, şüpheliyim ...
DÜZENLEME: Birkaç bilgi daha:
- Deneme / yakalamayı sadece
n++;
çizginin etrafına koymak performansı hala iyileştirir, ancak tüm bloğun etrafına koymak kadar değil
- Belirli bir istisnayı yakalarsanız (
ArgumentException
testlerimde) hala hızlı
- Özel durumu catch bloğunda yazdırırsanız hala hızlı
- Yakalama bloğundaki istisnayı yeniden yazarsanız tekrar yavaş olur
- Bir catch bloğu yerine son olarak bir blok kullanırsanız tekrar yavaşlar
- Sonunda bir blok ve bir yakalama bloğu kullanırsanız, hızlı
Tuhaf...
EDIT: Tamam, sökme var ...
Bu benim makinemde cordbg yok gibi mdbg ile sökme, C # 2 derleyici ve .NET 2 (32 bit) CLR kullanıyor. Hata ayıklayıcı altında bile aynı performans etkilerini görüyorum. Hızlı sürüm try
, değişken bildirimleri ve return deyimi arasındaki her şeyi, sadece bir catch{}
işleyici ile bir blok kullanır . Açıkçası yavaş sürümü denemek / yakalamak dışında aynıdır. Çağıran kod (yani Ana) her iki durumda da aynıdır ve aynı montaj temsilcisine sahiptir (bu nedenle satır içi bir sorun değildir).
Hızlı sürüm için demonte kod:
[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
Yavaş sürüm için demonte kod:
[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
Her durumda, *
hata ayıklayıcının basit bir "adım adım" içine girdiği şovlar.
DÜZENLEME: Tamam, şimdi kodu inceledim ve her sürümün nasıl çalıştığını görebildiğimi düşünüyorum ... ve daha az kayıt ve daha fazla yığın alanı kullandığı için daha yavaş sürümün daha yavaş olduğuna inanıyorum. Bunun küçük değerleri için n
muhtemelen daha hızlıdır - ancak döngü zamanın çoğunu aldığında daha yavaştır.
Muhtemelen try / catch bloğu daha fazla kaydediciyi kaydedilmeye ve geri yüklenmeye zorlar , böylece JIT döngü için de kullanır ... bu da genel performansı iyileştirir. JIT'in "normal" kodda çok fazla kayıt kullanmamasının makul bir karar olup olmadığı açık değildir .
EDIT: Bunu sadece x64 makinemde denedim. X64 CLR, bu koddaki x86 CLR'den çok daha hızlıdır (yaklaşık 3-4 kat daha hızlı) ve x64 altında try / catch bloğu fark edilir bir fark yaratmaz.