The Inquisitive Coder – Davy Brion's Blog

Trying to walk that thin line between intelligence and ignorance

Virtual Method Performance Penalty

Posted by Davy Brion on January 10th, 2010

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. 

18 Responses to “Virtual Method Performance Penalty”

  1. Sam Says:

    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.

  2. Al Says:

    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 ….

  3. Davy Brion Says:

    @Al

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

  4. Dmitri Maximov Says:

    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.

  5. The Morning Brew - Chris Alcock » The Morning Brew #514 Says:

    [...] Virtual Method Performance Penalty – Davy Brion talks about the alleged penalty in performance when working with Virtual methods in .NET, and tries some simple benchmarks which appear to show that there is little or no actual penalty in hi use case. [...]

  6. Davy Brion Says:

    @Dmitri

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

  7. den Ben Says:

    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? ;-)

  8. Davy Brion Says:

    @Ben

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

  9. Davy Brion Says:

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

  10. den Ben Says:

    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.

  11. Philip Rieck Says:

    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.

  12. Davy Brion Says:

    @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 ;)

  13. Interesting Finds: 2010 01.10 ~ 01.18 - gOODiDEA.NET Says:

    [...] Virtual Method Performance Penalty [...]

  14. Alex Yakunin Says:

    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.

  15. Alex Yakunin Says:

    > 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.

  16. Alex Yakunin Says:

    > 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.

  17. Alex Yakunin Says:

    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 —

  18. Alex Yakunin Says:

    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 ;)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>