上次我分別測試了類與結構體:http://www.BkJia.com/kf/201110/106554.html、密封類:http://www.BkJia.com/kf/201110/106555.html的函數調用速度評測。現在進行進一步分析,解讀編譯器生成的MSIL(微軟中間語言)代碼。
一、前期准備
先找到“IL 反匯編程序”(開始\程序\Microsoft Visual Studio 2010\Microsoft Windows SDK Tools\)——
運行“IL 反匯編程序”,打開編譯後的exe。展開節點,雙擊葉子節點查看MSIL代碼——
二、結果分析
然後我們將測試函數調用的那行代碼復制提取出來。如上圖的“IL_004c”行。
在復制提取過程中,發現VS2005與VS2010生成的函數調用代碼是完全一樣的。刪除啰嗦的名稱空間,將結果整理為表格——
觀察上面的表格,我們發現——
1.編譯的IL代碼時,並沒有做內聯(inline。將子函數展開)優化,而根據語義統統編譯為不同的調用(call)。看來優化工作是JIT(即時編譯器)負責的。
2.調用結構體是 方法調用(call instance)。JIT可根據此信息安排內聯優化。
3.調用派生類是 虛方法調用(callvirt instance)。因為被編譯為 調用基類的虛方法(PointerCall::Ptr),所以JIT認為其是正常的虛方法調用,不優化。
4.調用密封類是 虛方法調用(callvirt instance),與派生類調用一致。但由於其留下了類型信息(SldPointerCallAdd::Ptr),JIT發現它是一個密封類,於是安排內聯優化。
5.泛型方法雖然也是用call指令,但它帶有泛型參數,所以其行為與普通call調用不同。
6.結構體調用泛型方法時,會使用valuetype關鍵字。JIT可根據此信息安排優化(VS005的JIT有所優化;而VS2010的JIT將其進行徹底的內聯優化)。
附錄A、轉為接口時的IL代碼
派生類轉為接口——
IL_001d: ldloc.0
IL_001e: stloc.s V_4
密封類轉為接口——
IL_0020: ldloc.1
IL_0021: stloc.s V_5
結構體轉為接口——
IL_0023: ldloc.2
IL_0024: box TryPointerCall.SPointerCallAdd
IL_0029: stloc.s V_6
可見結構體轉為接口時多了裝箱操作,影響了性能。
附錄B、結構體泛型調用的IL代碼
接口泛型調用結構體——
IL_0391: ldloc.2
IL_0392: ldloc.s V_7
IL_0394: call uint8* TryPointerCall.PointerCallTool::CallPtr<valuetype TryPointerCall.SPointerCallAdd>(!!0,uint8*)
接口泛型調用結構體引用——
IL_03dd: ldloca.s V_2
IL_03df: ldloc.s V_7
IL_03e1: call uint8* TryPointerCall.PointerCallTool::CallRefPtr<valuetype TryPointerCall.SPointerCallAdd>(!!0&,uint8*)
可見泛型調用的IL代碼並不復雜,與普通調用基本一樣,也是先將參數放入堆棧再call。對於引用參數,將“ldloc.*”指令換成“ldloca.s”指令就行了。
zyl910的專欄