Why does the call
is faster than the callvirt
in IL?
I am exploring the C# via CLR book and I come across the following excerpt:
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.
And I can not wrap my head around that part: This additional check means that the callvirt IL instruction executes slightly more slowly than the call instruction.
. 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.
Could someone explain it in a foolproof manner?
@HansPassant answer to you already about the time difference between direct and indirect calls.
I want to add something that related directly to your question:
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.
Pay close attention to the text.
For call
instruction:
The call instruction assumes that this variable is not null
And for callvirt
instruction:
the JIT compiler generates code that verifies that the variable’s value is not null
The thing is that call
instruction not generating the null check. Yes as @Hans points out, the null check is only one assembly instruction, and it's almost for free, but it's important to know about the missing null check.
It's legal to use call
or callvirt
for both calls - virtual and non-virtual.
What's happening is that for all calls except those the compiler can be sure that the this
type isn't null or there is no this
, callvirt
will be used, in rest of the cases, call
will be used.
All of this is just from the Compiler\IL point of view. What happening later, is as @Hans wrote, direct call when it's possible or indirect call when it's required. But this will happen anyway whether there was a call
or a callvirt
.
For more info see IL Call Vs. Callvirt Instruction - You will find there more relevant links.