zoukankan      html  css  js  c++  java
  • 三探C#类与结构体究竟谁快——MSIL(微软中间语言)解读

    上次我分别测试了类与结构体(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

  • 相关阅读:
    Windows 8实例教程系列 开篇
    qt 开发发布于 windeploy.exe
    qt qoci 测试验证
    vmware vmx 版本不兼容
    qt oracle
    vc qt dll
    QOCIDriver unable to create environment
    qoci 编译完 放置位置 具体根据情况
    calling 'lastError' with incomplete return type 'QSqlError' qsqlquer
    Hbase 操作工具类
  • 原文地址:https://www.cnblogs.com/zyl910/p/2189403.html
Copyright © 2011-2022 走看看