【发布时间】:2020-03-25 17:58:24
【问题描述】:
为什么 call 在 IL 中比 callvirt 快?
我正在探索C# via CLR一书,我遇到了以下摘录:
call IL 指令可用于调用静态、实例和虚拟方法。打电话的时候 指令用于调用静态方法,必须指定定义该方法的类型 CLR 应该调用的。当调用指令用于调用实例或虚拟时 方法,您必须指定一个引用对象的变量。调用指令假定 此变量不为空。换句话说,变量本身的类型表明了哪种类型 定义 CLR 应该调用的方法。如果变量的类型没有定义方法, 检查基本类型的匹配方法。 call 指令经常用于调用一个 非虚拟的虚拟方法。
callvirt IL指令可以用来调用实例和虚方法,不是静态的 方法。当 callvirt 指令用于调用实例或虚方法时,你 必须指定一个引用对象的变量。当使用 callvirt IL 指令调用 一个非虚拟实例方法,变量的类型表示哪个类型定义了该方法 CLR 应该调用的。当使用 callvirt IL 指令调用虚拟实例时 方法,CLR 发现用于进行调用的对象的实际类型,然后 多态地调用方法。为了确定类型,使用的变量 拨打电话不得为空。换句话说,当编译这个调用时,JIT 编译器 生成验证变量值不为空的代码。如果为 null,则 callvirt 指令导致 CLR 抛出 NullReferenceException。这种额外的检查意味着 callvirt IL 指令的执行速度比 call 指令稍慢。 请注意,即使使用 callvirt 指令调用 非虚拟实例方法。
我无法理解那部分:This additional check means that the callvirt IL instruction executes slightly more slowly than the call instruction.。 call 和 callvirt 都假定对象不为空。因此,他们都应该检查对象是否不为空。因此速度应该是一样的。
谁能用万无一失的方式解释一下?
【问题讨论】:
-
非常不准确,作者极力避免谈论处理器执行C#代码的方式。它从不执行 IL,它执行由即时编译器生成的机器代码。处理器还有两个版本的 CALL 指令,一个以地址作为参数的直接调用和一个间接调用,其操作数说明地址的存储位置。后者用于虚拟呼叫。检索地址所需的额外读取会使虚拟调用变慢,这可能会导致管道泡沫。 NRE 检查实际上是免费的。
-
@HansPassant,谢谢。您的评论有助于我理解此事。如果您将其发布为答案,那么我将接受它。您能否详细说明管道点?你在说什么管道?
标签: instance clr virtual-functions cil