CLR Drilling Down: The Overhead of Method Calls[收藏]
.NET CLR通过Call和Callvirt两条指令支持三种方法调用模式(另一条MSIL指令Calli和这里讨论的内容关系不大):
l 直接方法调用(Direct Method Call):
这是最简单的方法调用模式,如果C#/VB.NET编译器可以静态的解析需要调用的函数,那么编译器就直接生成Call指令,并且把目标函数的地址(实际上是一个引用标志,理解成函数地址要容易一些)直接放在MSIL指令当中。在运行时CLR也不需要进一步解析,直接调用这个引用的函数就是。例如:
call instance void SomeType:: SomeMethod()
l 虚方法调用(Virtual Method Call):
这种方法和C++的vptr/vtable机制类似。由于subclass和override,编译器显然无法在编译期间识别出运行时需要调用的具体函数。所以编译器生成Callvirt指令,加上父类对应方法的引用。在运行时刻,每个Reference Type Object和Boxed ValueType Object都有一个指向其类型信息(MethodTable)的指针,MethodTable包含了这个类定义的所有方法(可能还会有重复),并且它的函数表前部顺序保证和父类MethodTable相同。这样CLR通过Object找到对应的MethodTable,然后从MethodTable中找到与指令中引用的方法对应位置的函数入口,然后调用该方法。
callvirt instance bool Object::Equels(Object o)
l 接口方法调用(Interface Method Call):
这是.NET独有的调用方式。由于仅支持单继承,MethodTable格式上只要与自己的父类一致。但是接口实现的时候仍然可能出现C++经常提到的钻石结构或者命名冲突。.NET的解决办法是引入Interface Map:编译器产生的代码和前面的Virtual Method Call完全一致,只是引用的方法是Interface的而不是父类的;在运行时刻MethodTable当中包含了一个InterfaceMap的指针,通过InterfaceMap,CLR可以找到MethodTable结构中和某个Interface结构顺序等价的一个段落,从而找到并且调用相应的接口函数。
callvirt instance bool IComparable::CompareTo(object o)
下图表示了这三种方法调用的路径。显然,直接调用通过JIT生成的机器指令最少,只需要一条x86 Call汇编指令,虚方法调用和C++的vptr调用类似,需要两条机器指令,包含一个间接调用;接口调用最慢,大约需要4条机器指令,包括间接调用。

接下来需要做的就是正确识别出在什么情况下编译器会选择哪种调用方式,例如下面代码中的方法调用,不妨尝试分析一下。J
interface ITest {
void SomeMethod();
}
class TestClass : ITest {
public virtual void SomeMethod2() {
//…
}
public void SomeMethod() {
//…
}
}
//…
ITest itest = …;
TestClass tobj = …;
itest.SomeMethod(); //?
tobj.SomeMethod(); //?
tobj.SomeMethod2(); //?
((TestClass)itest).SomeMethod(); //?
原文地址: http://blog.joycode.com/qqchen/posts/18890.aspx
浙公网安备 33010602011771号