【问题标题】:Why does the `call` is faster than the `callvirt` in IL?为什么`call`比IL中的`callvirt`快?
【发布时间】: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.callcallvirt 都假定对象不为空。因此,他们都应该检查对象是否不为空。因此速度应该是一样的。

谁能用万无一失的方式解释一下?

【问题讨论】:

  • 非常不准确,作者极力避免谈论处理器执行C#代码的方式。它从不执行 IL,它执行由即时编译器生成的机器代码。处理器还有两个版本的 CALL 指令,一个以地址作为参数的直接调用和一个间接调用,其操作数说明地址的存储位置。后者用于虚拟呼叫。检索地址所需的额外读取会使虚拟调用变慢,这可能会导致管道泡沫。 NRE 检查实际上是免费的。
  • @HansPassant,谢谢。您的评论有助于我理解此事。如果您将其发布为答案,那么我将接受它。您能否详细说明管道点?你在说什么管道?

标签: instance clr virtual-functions cil


【解决方案1】:

@HansPassant 已经回答你关于直接和间接调用之间的时间差。

我想添加一些与您的问题直接相关的内容:

调用和 callvirt 都假定对象不为空。因此,他们都应该检查对象是否不为空。因此速度应该是一样的。

密切关注文本。

对于call 指令:

调用指令假定该变量不为空

对于callvirt 指令:

JIT 编译器生成验证变量值不为空的代码

问题是call 指令没有生成空检查。是的,正如@Hans 指出的那样,空值检查只是一个汇编指令,而且几乎是免费的,但了解缺少的空值检查很重要。

对虚拟和非虚拟调用都使用callcallvirt 是合法的。 发生的情况是,对于除那些调用之外的所有调用,编译器可以确定 this 类型不为空或不存在 thiscallvirt 将被使用,在其余情况下,call 将是用过。

所有这些都只是从 Compiler\IL 的角度来看。后来发生的事情就像@Hans 所写的那样,在可能时直接调用或在需要时间接调用。但无论是call 还是callvirt,这都会发生。

有关更多信息,请参阅IL Call Vs. Callvirt Instruction - 您会在此处找到更多相关链接。

【讨论】:

  • cmets部分提到了什么样的管道?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-02
  • 2015-07-24
  • 1970-01-01
相关资源
最近更新 更多