글쎄, 당신이 물건을 타이밍하는 방식은 나에게 꽤 불쾌하게 보입니다. 전체 루프를 시간을 맞추는 것이 훨씬 합리적입니다.
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에서 최적화 된 JITted 코드를 봐야합니다.
편집 : 몇 가지 추가 정보 :
- try / catch를
n++;
라인 주위에두면 여전히 전체 성능을 향상 시키지만 전체 블록 주위에 두는 것만으로는 성능이 향상되지 않습니다.
ArgumentException
테스트에서 특정 예외를 발견하면 여전히 빠릅니다.
- catch 블록에 예외를 인쇄하면 여전히 빠릅니다.
- catch 블록에서 예외를 다시 발생 시키면 다시 느려집니다.
- catch 블록 대신 finally 블록을 사용하면 다시 느려집니다.
- catch 블록 뿐만 아니라 finally 블록을 사용하면 빠릅니다.
기묘한...
편집 : 좋아, 우리는 분해했다 ...
이것은 C # 2 컴파일러와 .NET 2 (32 비트) CLR을 사용하고 mdbg로 분해합니다 (내 컴퓨터에 cordbg가 없기 때문에). 디버거에서도 동일한 성능 효과를 볼 수 있습니다. 빠른 버전은 처리기 try
만으로 변수 선언과 return 문 사이의 모든 것을 둘러싼 블록을 사용 catch{}
합니다. 분명히 느린 버전은 try / 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
각각의 경우에 *
디버거가 간단한 "step-into"에 입력 된 위치를 보여줍니다.
편집 : 좋아, 이제 코드를 살펴 보았고 각 버전의 작동 방식을 볼 수 있다고 생각합니다 ... 레지스터가 적고 스택 공간이 많기 때문에 느린 버전은 느립니다. n
그것의 작은 값은 아마도 더 빠를 것입니다. 그러나 루프가 많은 시간을 차지하면 느려집니다.
try / catch 블록 은 더 많은 레지스터를 강제 로 저장 및 복원 할 수 있으므로 JIT는 루프에 대한 레지스터도 사용하므로 전반적인 성능을 향상시킵니다. JIT가 "정상"코드에서 많은 레지스터를 사용 하지 않는 것이 합리적인 결정인지는 확실 하지 않습니다 .
편집 : 방금 내 x64 컴퓨터에서 시도했습니다. x64 CLR 은이 코드에서 x86 CLR보다 훨씬 빠르며 (약 3-4 배 더 빠름), x64에서는 try / catch 블록이 눈에 띄는 차이를 만들지 않습니다.