为什么 `call` 比 IL 中的 `callvirt` 快?

Why does the `call` is faster than the `callvirt` in IL?

为什么 call 在 IL 中比 callvirt 快?

我正在探索 C# via CLR 一书,我发现了以下摘录:

The call IL instruction can be used to call static, instance, and virtual methods. When the call instruction is used to call a static method, you must specify the type that defines the method that the CLR should call. When the call instruction is used to call an instance or virtual method, you must specify a variable that refers to an object. The call instruction assumes that this variable is not null. In other words, the type of the variable itself indicates which type defines the method that the CLR should call. If the variable’s type doesn’t define the method, base types are checked for a matching method. The call instruction is frequently used to call a virtual method nonvirtually.

The callvirt IL instruction can be used to call instance and virtual methods, not static methods. When the callvirt instruction is used to call an instance or virtual method, you must specify a variable that refers to an object. When the callvirt IL instruction is used to call a nonvirtual instance method, the type of the variable indicates which type defines the method that the CLR should call. When the callvirt IL instruction is used to call a virtual instance method, the CLR discovers the actual type of the object being used to make the call and then calls the method polymorphically. In order to determine the type, the variable being used to make the call must not be null. In other words, when compiling this call, the JIT compiler generates code that verifies that the variable’s value is not null. If it is null, the callvirt instruction causes the CLR to throw a NullReferenceException. This additional check means that the callvirt IL instruction executes slightly more slowly than the call instruction. Note that this null check is performed even when the callvirt instruction is used to call a nonvirtual instance method.

我无法理解那部分:This additional check means that the callvirt IL instruction executes slightly more slowly than the call instruction.callcallvirt 都假定对象不为空。因此,他们都应该检查对象是否为空。结果速度应该是一样的。

谁能简单的解释一下?

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

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

Both the call and the callvirt assume that the object is not null. Hence, both of them should check that the object is not null. And as a result the speed should be the same.

仔细阅读正文。

对于call指令:

The call instruction assumes that this variable is not null

对于callvirt指令:

the JIT compiler generates code that verifies that the variable’s value is not null

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

对虚拟和非虚拟两种调用使用 callcallvirt 是合法的。 发生的情况是,对于除编译器之外的所有调用,编译器可以确定 this 类型不为 null 或没有 this,在其余情况下将使用 callvirt , call 将被使用。

以上都是Compiler\IL的观点。后来发生的事情,正如@Hans 所写,可能时直接调用,或在需要时间接调用。但是不管有 call 还是 callvirt.

无论如何都会发生这种情况

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