为什么call
比IL中的callvirt
快?
我正在通过 CLR 书探索 C#,我遇到了以下摘录:
调用IL 指令可用于调用静态方法、实例方法和虚拟方法。当呼叫时 指令用于调用静态方法,必须指定定义该方法的类型 CLR 应该调用。当调用指令用于调用实例或虚拟时 方法,则必须指定引用对象的变量。调用指令假定 此变量不为空。换句话说,变量本身的类型指示哪种类型 定义 CLR 应调用的方法。如果变量的类型未定义方法, 检查基本类型是否匹配方法。调用指令经常用于调用 非虚拟方法。
callvirt IL 指令可用于调用实例和虚拟方法,而不是静态方法 方法。当调用 callvirt 指令用于调用实例或虚拟方法时,您 必须指定引用对象的变量。当调用 IL 指令用于调用时 非虚拟实例方法,变量的类型指示定义该方法的类型 CLR 应该调用。当调用 IL 指令用于调用虚拟实例时 方法,CLR 发现用于进行调用的对象的实际类型,然后 以多态方式调用该方法。为了确定类型,变量用于 使调用不得为空。换句话说,在编译此调用时,JIT 编译器 生成验证变量的值是否为 null 的代码。如果为空,则调用 指令会导致 CLR 引发空引用异常。此附加检查意味着 调用 IL 指令的执行速度略慢于调用指令。 请注意,即使使用 callvirt 指令调用 非虚拟实例方法。
而且我无法绕开那部分:This additional check means that the callvirt IL instruction executes slightly more slowly than the call instruction.
.call
和callvirt
都假定对象不为 null。因此,它们都应该检查对象是否为空。因此,速度应该是相同的。
有人能以万无一失的方式解释它吗?
@HansPassant已经回答了您关于直接和间接呼叫之间的时差。
我想补充一些与您的问题直接相关的东西:
调用和调用都假定对象不为空。因此,它们都应该检查对象是否为空。因此,速度应该是相同的。
密切关注文本。
对于call
说明:
调用指令假定此变量不为空
对于callvirt
说明:
JIT 编译器生成验证变量的值是否为 null 的代码
问题是call
指令不生成空检查。是的,正如@Hans指出的那样,空检查只是一个汇编指令,它几乎是免费的,但了解缺少的空检查很重要。
对虚拟和非虚拟呼叫使用call
或callvirt
是合法的。 发生的情况是,对于除那些之外的所有调用,编译器可以确保this
类型不为 null 或没有this
,callvirt
将被使用,在其他情况下,将使用call
。
所有这些都只是从编译器\IL的角度来看。后来发生的事情,正如@Hans所写,在可能的情况下直接调用,或在需要时间接调用。但无论如何,这都会发生,无论是call
还是callvirt
.
有关更多信息,请参阅 IL 呼叫与呼叫说明 - 您将找到更多相关链接。