程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 三探C#類與結構體究竟誰快——MSIL(微軟中間語言)解讀

三探C#類與結構體究竟誰快——MSIL(微軟中間語言)解讀

編輯:C#入門知識

 

上次我分別測試了類與結構體: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生成的函數調用代碼是完全一樣的。刪除啰嗦的名稱空間,將結果整理為表格——

模式 MSIL 亮點 靜態調用 call       uint8*  TryIt_Static_Ptr(uint8*) 靜態函數 調用派生類 callvirt   instance uint8*  PointerCall::Ptr(uint8*) 虛方法 調用密封類 callvirt   instance uint8*  SldPointerCallAdd::Ptr(uint8*) 虛方法 調用結構體 call       instance uint8*  SPointerCallAdd::Ptr(uint8*) 方法(非虛) 調用基類 callvirt   instance uint8*  PointerCall::Ptr(uint8*) 虛方法 調用派生類的接口 callvirt   instance uint8*  IPointerCall::Ptr(uint8*) 虛方法 調用密封類的接口 callvirt   instance uint8*  IPointerCall::Ptr(uint8*) 虛方法 調用結構體的接口 callvirt   instance uint8*  IPointerCall::Ptr(uint8*) 虛方法 基類泛型調用派生類 call       uint8*  CallClassPtr<class PointerCallAdd>(!!0, uint8*) class 基類泛型調用基類 call       uint8*  CallClassPtr<class PointerCall>(!!0, uint8*) class 接口泛型調用派生類 call       uint8* CallPtr<class  PointerCallAdd>(!!0, uint8*) class 接口泛型調用密封類 call       uint8* CallPtr<class  SldPointerCallAdd>(!!0, uint8*) class 接口泛型調用結構體 call       uint8*  CallPtr<valuetype SPointerCallAdd>(!!0, uint8*) valuetype 接口泛型調用結構體引用 call       uint8*  CallRefPtr<valuetype SPointerCallAdd>(!!0&, uint8*) valuetype 接口泛型調用基類 call       uint8* CallPtr<class  PointerCall>(!!0, uint8*) class 接口泛型調用派生類的接口 call       uint8* CallPtr<class  IPointerCall>(!!0, uint8*) class 接口泛型調用密封類的接口 call       uint8* CallPtr<class  IPointerCall>(!!0, uint8*) class 接口泛型調用結構體的接口 call       uint8* CallPtr<class  IPointerCall>(!!0, uint8*) class

觀察上面的表格,我們發現——
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的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved