The goal is to maximize performance in a certain part of the application. Having read some articles of Habr and somewhere once having heard or read something, I began, among other things, to seal classes and methods (and also to turn some small classes into structures). Already zakommitiv, decided in a small application to check, and whether it really gives the result.

class X { public int NonVirtual() => DateTime.Now.Millisecond; public virtual int Virtual() => DateTime.Now.Minute; } class Y : X { public override int Virtual() => DateTime.Now.Millisecond; } class Program { private static volatile int TST = 0; static int Slow(Y x) => x.Virtual(); static int Fast(Y y) => y.NonVirtual(); static void Main(string[] args) { int i = 0; var stopwatch1 = new Stopwatch(); var stopwatch2 = new Stopwatch(); stopwatch1.Start(); var y = new Y(); for (i = 0; i < 100000000; i++) { TST = Fast(y); } stopwatch1.Stop(); Console.WriteLine("Time elapsed: {0}", stopwatch1.Elapsed); stopwatch2.Start(); for (i = 0; i < 100000000; i++) { TST = Slow(y); } stopwatch2.Stop(); Console.WriteLine("Time elapsed: {0}", stopwatch2.Elapsed); Console.ReadKey(); } } 

The TST field is declared volatile so that the compiler does not optimize calls.

At first I was surprised that there was no difference at all, then the first cycle is faster by a couple of tens of milliseconds, then the second (although the virtual method tables and all that, it is logical to assume that the virtual method should lag a little).

Then I got into IL:

 IL_0001: callvirt instance int32 PerformanceTests.X::Virtual() 

This is a virtual method call. It feels good. Second call:

 IL_0001: callvirt instance int32 PerformanceTests.X::NonVirtual() 

I'm sorry, what? callvirt ? Shouldn't there be a call ? sealed also does not affect the call to the virtual method.

I would like to find out from more experienced colleagues, nevertheless, is there any point in sealing classes in terms of performance? And also, why is the call of a non-virtual function in IL the same as a virtual one?

Update: In the comments, @Grundy wrote that callvirt is because the method is declared in the base class. Rewrote the code so that the base class is now used (i.e. Y is not used). callvirt is called.

  • one
    Have you already passed the optimization stage using the selection of more suitable data structures and algorithms? - tym32167
  • one
    Shouldn't there be a call here? - no, since the method is declared in the base class - Grundy
  • @ tym32167, everything is optimized there in parallel, just if I touch a class, I make it sealed . - A1essandro
  • one
    As far as I know, the call instruction does not check the first argument ( this ) for null , and since the C # semantics requires such a check, the compiler uses callvirt . - PetSerAl
  • one
    For example: sharplab.io ... in three cases, the call is made using call and in one with callvirt . - PetSerAl pm

1 answer 1

As already said in the comments, the compiler uses callvirt to generate a NullReferenceException .

To get a clear call instruction, the compiler must be sure that a class instance cannot be null . Example:

 class Test { public void Method() => Console.WriteLine(123); } static void Main(string[] args) { new Test().Method(); } 

IL code:

 IL_0001: newobj instance void ConsoleApp1.Program/Test::.ctor() IL_0006: call instance void ConsoleApp1.Program/Test::Method() 

If the code is slightly changed:

 static Test GetTest() => new Test(); static void Main(string[] args) { GetTest().Method(); } 

That is already getting callvirt , since the compiler assumes that GetTest can return null:

 IL_0001: call class ConsoleApp1.Program/Test ConsoleApp1.Program::GetTest() IL_0006: callvirt instance void ConsoleApp1.Program/Test::Method() 

sealed does not affect anything at runtime. This is just a developer marker that tells you what to do in high-level code and what not.

For each non-virtual callvirt JIT method, insert one additional instruction before each call :

 cmp dword ptr [/*здесь регистр с адресом экземляра*/],ecx call 00007FF9CD540098 // метод 

The effect of a single cmp instruction is very slight.

  • About runtime it is clear. I simply did not take into account that the compiler makes a decision based on more complex logic than just the virtuality of the method. - A1essandro