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

來源:互聯網
上載者:User

上次我分別測試了類與結構體(http://www.cnblogs.com/zyl910/archive/2011/09/19/2186623.html)、密封類(http://www.cnblogs.com/zyl910/archive/2011/09/20/2186622.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”指令就行了。

(完)

 

目錄——
C#類與結構體究竟誰快——各種函數調用模式速度評測:http://www.cnblogs.com/zyl910/archive/2011/09/19/2186623.html
再探C#類與結構體究竟誰快——考慮棧變數、棧分配、64位整數、密封類:http://www.cnblogs.com/zyl910/archive/2011/09/20/2186622.html
三探C#類與結構體究竟誰快——MSIL(微軟中繼語言)解讀:http://www.cnblogs.com/zyl910/archive/2011/09/24/2189403.html
四探C#類與結構體究竟誰快——跨程式集(assembly)調用:http://www.cnblogs.com/zyl910/archive/2011/10/01/2197844.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.