Why Virtual Methods Are Slower in C#

\clrc

I read that virtual calls make the code slower than calling non-virtual ones in C#.
However, the IL instruction for both are the same callvirt except in cases where base.somemethod() is called.

So how does virtual method hurt performance?

Best Answer

As pointed out here, calling callvirt is slower, but by a very small margin. I'm not sure how you're getting the CIL code, but as Eric Gunnerson points out, .NET never uses call for instance classes. It always uses callvirt, and he even states in a follow-up post that the difference in performance impact is minimum.

Just a very quick test, where all classes have a void PrintTest() method (which only prints a message to the console)...

  1. BaseVirtual is a base class with the method defined as virtual.
  2. DerivedVirtual and DerivedVirtual2 use override to redefine the virtual method, inheriting from BaseVirtual.
  3. Base is a regular class with a regular instance method (no virtual or sealed).
  4. Seal is a sealed class, just for the kicks.
  5. Stat is a class with the method defined as static.

Here's the main code:

using System;

namespace CSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            BaseVirtual baseVirtual = new BaseVirtual();
            DerivedVirtual derivedVirtual = new DerivedVirtual();
            DerivedVirtual2 derivedVirtual2 = new DerivedVirtual2();
            Base regularBase = new Base();
            Seal seal = new Seal();

            baseVirtual.PrintTest();
            derivedVirtual.PrintTest();
            derivedVirtual2.PrintTest();
            regularBase.PrintTest();
            seal.PrintTest();
            Stat.PrintTest();
        }
    }
}

And, here's the CIL code:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       68 (0x44)
  .maxstack  1
  .locals init ([0] class CSharp.BaseVirtual baseVirtual,
           [1] class CSharp.DerivedVirtual derivedVirtual,
           [2] class CSharp.DerivedVirtual2 derivedVirtual2,
           [3] class CSharp.Base regularBase,
           [4] class CSharp.Seal seal)
  IL_0000:  newobj     instance void CSharp.BaseVirtual::.ctor()
  IL_0005:  stloc.0
  IL_0006:  newobj     instance void CSharp.DerivedVirtual::.ctor()
  IL_000b:  stloc.1
  IL_000c:  newobj     instance void CSharp.DerivedVirtual2::.ctor()
  IL_0011:  stloc.2
  IL_0012:  newobj     instance void CSharp.Base::.ctor()
  IL_0017:  stloc.3
  IL_0018:  newobj     instance void CSharp.Seal::.ctor()
  IL_001d:  stloc.s    seal
  IL_001f:  ldloc.0
  IL_0020:  callvirt   instance void CSharp.BaseVirtual::PrintTest()
  IL_0025:  ldloc.1
  IL_0026:  callvirt   instance void CSharp.BaseVirtual::PrintTest()
  IL_002b:  ldloc.2
  IL_002c:  callvirt   instance void CSharp.BaseVirtual::PrintTest()
  IL_0031:  ldloc.3
  IL_0032:  callvirt   instance void CSharp.Base::PrintTest()
  IL_0037:  ldloc.s    seal
  IL_0039:  callvirt   instance void CSharp.Seal::PrintTest()
  IL_003e:  call       void CSharp.Stat::PrintTest()
  IL_0043:  ret
} // end of method Program::Main

Nothing too fancy, but this shows that only the static method used call. Even so, I don't think this will really make a difference for most applications, so we shouldn't sweat on the small stuff and worry about this.

So, in conclusion, as the SO post states, you may get a performance hit because of the lookup the runtime needs to call the virtual methods. Compared to static methods, you get the overhead of callvirt.

... Well, that was fun...

Related Topic