Virtual Method Performance Penalty

20 commentsWritten on January 10th, 2010 by
Categories: C#, Performance

You often hear/read that one of the reasons why C# methods aren’t virtual by default is because of performance.  Calling a virtual method is more expensive than calling a regular instance method, because the CLR has to determine the correct override to call at runtime, instead of being able to simply call the instance method directly.  Another reason why virtual methods are more expensive to call is because they can never be inlined.

Is it really that much more expensive though? I ran a little experiment and i’d like to share the results with you.

Suppose you have the following 2 classes:

    public class MyClass

    {

        protected long someLong;

 

        public void IncreaseLong()

        {

            someLong++;

        }

 

        public virtual void VirtualIncreaseLong()

        {

            someLong++;

        }

    }

 

    public class MyDerivedClass : MyClass

    {

        public override void VirtualIncreaseLong()

        {

            someLong += 2;

        }

    }

 

As you can see, there is no difference between the IncreaseLong and VirtualIncreaseLong methods, except that the latter is virtual and the former is a regular instance method.  According to many people, calling VirtualIncreaseLong instead of IncreaseLong will be more expensive.  I also have a derived class which overrides the VirtualIncreaseLong method with a slightly different implementation.

If we call these methods a bunch of times (like 1000000000 times), we should notice quite a difference according to many people.

I wrote the following test code which calls these methods a bunch of times, times it, and outputs the results.

    class Program

    {

        const int numberOfTimes = 1000000000;

 

        static void Main(string[] args)

        {

            var myObject = new MyClass();

            var myDerivedObject = new MyDerivedClass();

 

            // we do this so there's no first-time performance cost while timing

            EnsureThatEverythingHasBeenJitted(myObject);

            EnsureThatEverythingHasBeenJitted(myDerivedObject);

 

            TestNormalIncreaseMethod(myObject);

            TestVirtualIncreaseMethod(myObject);

 

            TestNormalIncreaseMethod(myDerivedObject);

            TestVirtualIncreaseMethod(myDerivedObject);

 

            Console.ReadLine();

        }

 

        static void EnsureThatEverythingHasBeenJitted(MyClass theObject)

        {

            theObject.IncreaseLong();

            theObject.VirtualIncreaseLong();

        }

 

        static void TestNormalIncreaseMethod(MyClass theObject)

        {

            Console.WriteLine(string.Format("calling the IncreaseLong method of type {0} {1} times", theObject.GetType().Name, numberOfTimes));

 

            var stopwatch = Stopwatch.StartNew();

            for (var i = 0; i < numberOfTimes; i++)

            {

                theObject.IncreaseLong();

            }

            stopwatch.Stop();

 

            Console.WriteLine("Elapsed milliseconds: " + stopwatch.ElapsedMilliseconds);

        }

 

        static void TestVirtualIncreaseMethod(MyClass theObject)

        {

            Console.WriteLine(string.Format("calling the VirtualIncreaseLong method of type {0} {1} times", theObject.GetType().Name, numberOfTimes));

 

            var stopwatch = Stopwatch.StartNew();

            for (var i = 0; i < numberOfTimes; i++)

            {

                theObject.VirtualIncreaseLong();

            }

            stopwatch.Stop();

 

            Console.WriteLine("Elapsed milliseconds: " + stopwatch.ElapsedMilliseconds);

        }

    }

 

The output of running this code might surprise you.  On my machine, i got the following results when the code was compiled in debug mode:

manual_compile_debug

The difference between calling the regular instance method and the virtual method is quite small.  I’d even say it’s negligible.

When compiling in release mode, i got the following output:

manual_compile_optimized

I ran the test a bunch of times, and there was no consistent observable performance penalty when calling the virtual methods.  In fact, the virtual methods often performed faster than the regular instance methods and in most cases were equally fast.  I’m not claiming that virtual methods are faster than regular instance methods, but if there really was an extra real-world performance cost associated with virtual methods, it surely should be observable with this test code, no?

Obviously, this test isn’t scientific in any way.  But still, i think it does show that the so called performance cost associated with virtual methods is highly overrated.  There definitely will be cases where virtual methods are more expensive than regular instance methods, but i’m willing to bet that those cases are rare and that the vast majority of .NET developers will never be negatively impacted by it. 

Side note: have you ever noticed that most people who recommend to avoid virtual methods due to their performance cost never put the same emphasis on avoiding the cost of say, frequent remote operations?  Which is odd, since i wouldn’t be surprised if that would be the most common reason for performance problems with .NET applications.  Then again, that’s what you get when the biggest company pushing a platform advocates meaningless performance improvements while at the same time pushing bad architectural decisions/guidelines on the world because the resulting code is supposedly easier to write, use and maintain.

You can download the example code here so you can run the test yourself. 

  • Sam

    I’ve found similar results when dealing with delegate calls and lambda functions.

    My test also showed that the first execution of a method call has a more significant impact.

    It really emphasizes the principle measure, test, measure and test some more.

  • Al

    Java has the opposite convention – all methods are virtual unless declared as final.

    I sometimes wonder if MS did it the other way round just so they could be deliberately different (in the beginning C# was very similar to Java – still is, but less so now as C# has evolved more successfully)

    No matter, you’d have to be writing a seriously performance-sensitive application for it to make any difference – in which case you’d probably not be using a language with automatic garbage collection anyway ….

  • http://davybrion.com Davy Brion

    @Al

    Anders Hejlsberg discusses his reasons for making it non-virtual by default here:
    http://www.artima.com/intv/nonvirtual.html

  • http://x-tensive.com Dmitri Maximov

    Hello Davy,

    I suppose that this might be interesting for you: The cost of method calls (http://tips.x-tensive.com/2008/10/method-call-performance.html)

    Virtual method calls are compared with delegate method calls as well as with interface method calls. Moreover, delegate creation & generic method calls are also measured. Take a look at it.

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #514

  • http://davybrion.com Davy Brion

    @Dmitri

    i don’t see anything in that post that explains the behavior i’m seeing here

  • http://benpittoors.wordpress.com den Ben

    My guess is that subsequent virtual method calls are cached (not the result offcourse, the call itself).

    To be able to measure the performance correctly you would need multiple classes… maybe quicknet it? ;-)

  • http://davybrion.com Davy Brion

    @Ben

    i tried it with new instances for each call, the performance measurements of calling the methods were pretty much identical

  • http://davybrion.com Davy Brion

    and yes, i allocated the new instances _before_ i started timing obviously, so i don’t think it’s just a caching thing

  • http://benpittoors.wordpress.com den Ben

    I meant using multiple classes.. as in types, not instances.

    Once a vtable is loaded for a certain type, I imagine the performance cost to be pretty low.

  • Philip Rieck

    You can see some of the reasons for what you’re seeing here and here (both go to Eric Gunnerson’s blog).

    Basically, there are two IL instructions to call a method, “call” and “callvirt”. Since the c# compiler is emitting “callvirt” for both virtual and non-virtual method calls, you don’t see any difference in performance between the virtual and non-virtual call.

  • http://davybrion.com Davy Brion

    @Philip

    there is still a difference between using callvirt for a non-virtual method and for a virtual method AFAIK. When using callvirt with a virtual function, the CLR still does a null reference check (and throws a NullReferenceException if the instance reference is null) while it does the null check and an aditional lookup of the correct virtual method to call in case of a virtual method. I had always been told that that was supposedly the reason why there should be a performance difference. AFAIK, the call instruction is only used for static methods.

    If there is indeed no performance cost at all, then certain Microsoft people really need to stop using that as an argument ;) . And it should, in fact be removed as a reason why not to use virtual methods from the Framework Design Guidelines book that they publish. And they might want to update the CLR Via C# book regarding this as well ;)

  • Pingback: Interesting Finds: 2010 01.10 ~ 01.18 - gOODiDEA.NET

  • http://ormbattle.net Alex Yakunin

    Two remarks on your tests:

    1) I’d recommend to enroll the main loop 10 times at least. Increment + comparison cost is comparable to VTable lookup.

    2) EnsureThatEverythingHasBeenJitted actually doesn’t work: TestXxxMethod isn’t jitted after this call, and consequently, Stopwatch code isn’t jitted as well. Not really important in your case (2…5 sec. is pretty large time in comparison to JITter costs), but I’d anyway call TestXxxMethods from EnsureThatEverythingHasBeenJitted, but with small numberOfTimes value.

    3) If 1 & 2 won’t affect on result, I’d try to use a bit different code to avoid possible VTable lookup caching:
    theObject1 = (MyObject) (object) theObject;

    theObjectN = (MyObject) (object) theObject;
    — Loop start —
    theObject1.VirtualIncreaseLong();

    theObjectN.VirtualIncreaseLong();
    — Loop end —

    Not fully sure if this will work, since a lot depends on compiler here. But it’s clear that original code is simply ideal for VTable lookup caching.

  • http://ormbattle.net Alex Yakunin

    > Since the c# compiler is emitting “callvirt” for both virtual and non-virtual method calls, you don’t see any difference in performance between the virtual and non-virtual call.

    That’s wrong. JITter emits different code for callvirt that actually invoking non-virtual method.

    The main reason to always emit callvirt by C# is that actually referenced method can be replaced by virtual in future versions of assembly it is declared in, so using callvirt is safe from the point of compatibility.

  • http://ormbattle.net Alex Yakunin

    > i tried it with new instances for each call, the performance measurements of calling the methods were pretty much identical

    Surely: allocation is ~ 4-5 times more costly than vmcall.

  • http://ormbattle.net Alex Yakunin

    A bit better idea to avoid any possible VMCall caching:

    // Objects must be different
    theObject1 = theObject.Clone();

    theObject2 = theObject.Clone();

    — Loop start —
    theObject1.VirtualIncreaseLong();

    theObjectN.VirtualIncreaseLong();

    // Now we’re rolling all the references to ensure all of them are changed
    theObjectT = theObjectN;
    theObjectN = theObjectN-1;
    theObjectN-1 = theObjectN-2;

    theObject2 = theObject1;
    theObject1 = theObjectT;
    — Loop end —

  • http://ormbattle.net Alex Yakunin

    Finally, there are no reasons to cache VMTable lookups – they anyway take constant (and very small) time.

    VMTable lookup caching can be practically useful only for interface calls, since method address resolution is more complex in this case.

    So forget about idea #3 ;)

  • eeeeaaii

    if you have 100 subclasses of class A, and they all override a method a, it will take a lot longer for it to figure out which version of a to call. Think of it as a switch statement with one case label verses a switch statement with 100 case labels. Since you’re just testing it with one method it’s not surprising that the cost is negligible.

  • Pingback: Virtual Method Performance Penalty, Revisited | The Inquisitive Coder – Davy Brion's Blog