在對.net程序進行調試或者性能測試時,常常需要查看生成的IL代碼,但僅僅有IL代碼還是不夠的,有時我們還希望查看CLR生成的最終asm代碼。在VS裡,可以非常方便的查看最終的asm代碼:當程序執行到斷點時,在代碼窗口右鍵選擇Go To Disassemble就可以。但是,當通過VS Debug程序時,為了方便調試,CLR通常不會生成最優化的代碼。所以為了得到實際運行時的asm代碼,還必須做以下設置:
1,在Release模式下編譯代碼;
2. 打開工程屬性窗口,選擇”Build”頁面--- “Advanced”,彈出窗口的“Debug Info”項設置為”pdb-only”。
3. 打開Tools => Options => Debugging => General,保證Suppress JIT optimization on module load和Enable Just My Code處於未選中狀態。
可以在這裡找到關於配置的更多資料。
我對.net 數學庫做了一系列測試,結果可謂喜憂參半。當然,不同機器上可能得到不同的asm代碼,以下是我的測試配置:intel Q6600 + .net framework 3.5 sp1。下面就來看看System.Math下個函數的性能。測試代碼如下:
array[i] = Math.XXX(array[i]);
1. Math.Sqrt, Math.Sin, Math.Cos是所能期望的最理想實現,三個函數分別直接映射為fsqrt, fsin和fcos三條浮點匯編指令,可以認為這3個函數與匯編代碼的效率一樣。
array[i] = Math.Sqrt(array[i]);
00000092 fild dword ptr [eax+edx*4+8]
00000096 fsqrt
00000098 fstp qword ptr [ebx+edx*8+8]
array[i] = Math.Sin(array[i]);
00000092 fild dword ptr [eax+edx*4+8]
00000096 fsin
00000098 fstp qword ptr [ebx+edx*8+8]
2. Math.Asin, Math.Acos, Math.Tan, Math.Atan, Math.floor, Math.Cell, Math.Log,Math.Exp,Math.Floor,Math.Pow,Math.Round以及其他所有以h結尾的三角函數:在Disassemble下,只能看到這些函數並沒有inline。下面是Math.Tan函數的disassemble代碼,在VS裡是無法訪問7935A4AB處的代碼(實際上這個地址也根本不正確,這是vs裡一個邪惡的bug):
double tan = Math.Tan(c3.X);
00000219 fld dword ptr [ebp-30h]
0000021c sub esp,8
0000021f fstp qword ptr [esp]
00000222 call 7935A4AB
00000227 fstp qword ptr [ebp-38h]
查看這幾個函數的IL代碼,可以發現它們都被標記為” cil managed internalcall”。MS所有文檔中對這個標記的解釋都非常少,實際上它們將調用一些內部的非托管代碼。在SOS的幫助下,可以發現Math.Tan的實際地址位於7A2C37FB,相應的代碼則是:
Unmanaged code
7A2C37FB 55 push ebp
7A2C37FC 8BEC mov ebp,esp
7A2C37FE DD4508 fld qword ptr [ebp+8]
7A2C3801 D9F2 fptan
7A2C3803 DDD8 fstp st(0)
7A2C3805 5D pop ebp
7A2C3806 C20800 ret 8
7A2C3809 55 push ebp
7A2C380A 8BEC mov ebp,esp
7A2C380C DD4508 fld qword ptr [ebp+8]
對於Math.Tan,最終仍然生成了fpan這樣的cpu指令,但為什麼和sin的差別會那麼大呢,確實比較奇怪。至於另外剩下的函數,雖然算法不同,但實現的手段都是類似的,都是用非托管代碼所寫,並且可能導致多次對其他內部函數的調用。感興趣可以用sos逐個查看。
3. Math.Abs. 這個函數比較特別,參數類型不同,所生成的代碼也不同。對於浮點數來說,將會直接映射為浮點匯編指令fabs。對於int,卻出乎意料的復雜。代碼會想檢查參數是否為負數,如果是,則需要進一步調用函數System.Math.AbsHelper,這是Math類的一個私有方法,它會進一步檢查數據是否會溢出。如果不考慮安全性,自己編寫一個簡單的整數絕對值表達式要高效很多:
//asm code for Math.Abs(float/double)
00000086 fld qword ptr [ebx+edx*8+8]
0000008a fabs
0000008c fstp qword ptr [ebx+edx*8+8]
//asm code for Math.Abs(integer)
0000008e mov ecx,dword ptr [eax+esi*4+8]
00000092 test ecx,ecx
00000094 jl 0000009A
00000096 mov eax,ecx
00000098 jmp 0000009F
0000009a call 75F1E728 //call Math.AbsHelper if integer is a negative number
0000009f mov dword ptr [ebp-24h],eax
000000a2 fild dword ptr [ebp-24h]
000000a5 cmp esi,dword ptr [ebx+4]
000000a8 jae 00000286
000000ae fstp qword ptr [ebx+esi*8+8]
//asm code for Math.AbsHelper
00000000 push ebp
00000001 mov ebp,esp
00000003 push eax
00000004 cmp ecx,80000000h
0000000a je 00725A00
00000010 neg ecx
00000012 mov eax,ecx
00000014 mov esp,ebp
00000016 pop ebp
00000017 ret //return if no overflow occurred
00000018 mov ecx,7994E990h
0000001d call FF83A548
00000022 mov dword ptr [ebp-4],eax
00000025 mov edx,790C1000h
0000002a mov ecx,70005A82h
0000002f call FF83A598
00000034 mov ecx,eax
00000036 call FF8641C8
0000003b mov edx,eax
0000003d mov ecx,dword ptr [ebp-4]
00000040 call FFDCD44C
00000045 mov ecx,dword ptr [ebp-4]
00000048 call FF83A5B0
0000004d int 3
//optimized unsafe abs:
static public int FastAbs(int a)
{
return (a >= 0) ? a : -a;
}
//asm code for unsafe abs which is inlined
00000091 mov eax,dword ptr [ebx+ecx*4+8]
00000095 test eax,eax
00000097 jge 0000009D
00000099 neg eax
0000009b jmp 0000009D
0000009d cmp ecx,edx
0000009f jae 00000296
000000a5 mov dword ptr [ebx+ecx*4+8],eax
4. Math.Max, Math.Min則是用普通托管語言編寫的代碼,沒有特別優化,但這2個方法是inline的。