Chà, cái cách mà bạn sắp xếp thời gian có vẻ khá khó chịu với tôi. Sẽ hợp lý hơn nhiều nếu chỉ tính thời gian cho toàn bộ vòng lặp:
var stopwatch = Stopwatch.StartNew();
for (int i = 1; i < 100000000; i++)
{
Fibo(100);
}
stopwatch.Stop();
Console.WriteLine("Elapsed time: {0}", stopwatch.Elapsed);
Bằng cách đó, bạn không phải chịu sự chi phối của thời gian nhỏ, số học dấu phẩy động và lỗi tích lũy.
Đã thực hiện thay đổi đó, hãy xem liệu phiên bản "không bắt" có chậm hơn phiên bản "bắt" hay không.
EDIT: Được rồi, tôi đã thử nó - và tôi thấy kết quả tương tự. Rất kỳ quặc. Tôi tự hỏi liệu thử / bắt có vô hiệu hóa một số nội tuyến xấu hay không, nhưng sử dụng [MethodImpl(MethodImplOptions.NoInlining)]
thay vào đó không giúp ...
Về cơ bản, bạn sẽ cần xem mã JITted được tối ưu hóa theo cordbg, tôi nghi ngờ ...
EDIT: Một vài thông tin nữa:
- Đặt thử / bắt xung quanh chỉ
n++;
dòng vẫn cải thiện hiệu suất, nhưng không nhiều bằng đặt nó xung quanh toàn bộ khối
- Nếu bạn bắt được một ngoại lệ cụ thể (
ArgumentException
trong các thử nghiệm của tôi) thì nó vẫn nhanh
- Nếu bạn in ngoại lệ trong khối bắt, nó vẫn nhanh
- Nếu bạn nghĩ lại ngoại lệ trong khối bắt, nó sẽ chậm lại
- Nếu bạn sử dụng khối cuối cùng thay vì khối bắt, nó sẽ chậm lại
- Nếu bạn sử dụng khối cuối cùng cũng như khối bắt, nó sẽ nhanh
Kỳ dị...
EDIT: Được rồi, chúng tôi đã tháo gỡ ...
Đây là sử dụng trình biên dịch C # 2 và .NET 2 (32-bit) CLR, phân tách bằng mdbg (vì tôi không có cordbg trên máy của mình). Tôi vẫn thấy các hiệu ứng hiệu suất tương tự, ngay cả dưới trình gỡ lỗi. Phiên bản nhanh sử dụng một try
khối xung quanh mọi thứ giữa các khai báo biến và câu lệnh return, chỉ với một catch{}
trình xử lý. Rõ ràng phiên bản chậm là như nhau ngoại trừ không có thử / bắt. Mã gọi (tức là Chính) giống nhau trong cả hai trường hợp và có cùng đại diện lắp ráp (vì vậy đây không phải là vấn đề nội tuyến).
Mã tháo rời cho phiên bản nhanh:
[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
Mã tháo rời cho phiên bản chậm:
[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
Trong mỗi trường hợp, *
chương trình trình gỡ lỗi nhập vào một "bước vào" đơn giản.
EDIT: Được rồi, giờ tôi đã xem qua mã và tôi nghĩ rằng tôi có thể thấy mỗi phiên bản hoạt động như thế nào ... và tôi tin rằng phiên bản chậm hơn chậm hơn vì nó sử dụng ít thanh ghi hơn và nhiều không gian ngăn xếp hơn. Đối với các giá trị nhỏ n
có thể nhanh hơn - nhưng khi vòng lặp chiếm phần lớn thời gian, nó sẽ chậm hơn.
Có thể khối thử / bắt buộc phải lưu và khôi phục nhiều thanh ghi hơn, do đó, JIT cũng sử dụng các thanh ghi cho vòng lặp ... điều này xảy ra để cải thiện hiệu suất tổng thể. Không rõ liệu đó có phải là một quyết định hợp lý cho JIT không sử dụng nhiều đăng ký trong mã "thông thường" hay không.
EDIT: Chỉ cần thử điều này trên máy x64 của tôi. CLR x64 nhanh hơn nhiều (nhanh hơn khoảng 3-4 lần) so với CLR x86 trên mã này và dưới x64, khối thử / bắt không tạo ra sự khác biệt đáng chú ý.