Monday, February 26, 2007 7:58 AM bart

Virtual vs. non-virtual method calls from a perf perspective

Last week, I was doing an introduction session on C# for students with a Java background. There were quite some interesting discussions around the why and how of properties, indexers, operator overloads, events, delegates, attributes, etc compared to "equivalents" in Java. I won't repeat this discussion over here again but would like to pay a bit attention to another difference: virtual versus non-virtual methods. Today, I read the following question on a discussion list for .NET:

Anybody know why methods aren't virtual by default in .NET?
It seems like a really bad default to have all methods non-virtual.

Basically, in Java methods are virtual by default, unless you declare them as "final". In C#, it's the other way around: methods are non-virtual by default, unless you declare them as "virtual". The reasons why are pointed out by Anders Hejlsberg on Artima.

In this post, I just wanted to put the performance aspect in perspective by means of a little demo app:

1 using System; 2 using System.Diagnostics; 3 4 class Virt 5 { 6 public virtual void Do() {} 7 public void DoIt() {} 8 9 static void Main() 10 { 11 Virt v = new Virt(); 12 13 Stopwatch sw = new Stopwatch(); 14 sw.Start(); 15 for (int i = 0; i < 1000000000; i++) 16 v.DoIt(); 17 sw.Stop(); 18 Console.WriteLine(sw.Elapsed); 19 20 sw.Reset(); 21 sw.Start(); 22 for (int i = 0; i < 1000000000; i++) 23 v.Do(); 24 sw.Stop(); 25 Console.WriteLine(sw.Elapsed); 26 } 27 }

Make a little estimate of the figures (relative to each other) printed out on lines 18 and 25. You might be a little surprised... (Tip: do compile the code fragments once with the /o csc.exe flag too and observe the difference)

For ILDASM freaks, here are the IL equivalents of both methods:

.method public hidebysig newslot virtual instance void Do() cil managed { // Code size 2 (0x2) .maxstack 8 IL_0000: nop IL_0001: ret } // end of method Virt::Do .method public hidebysig instance void DoIt() cil managed { // Code size 2 (0x2) .maxstack 8 IL_0000: nop IL_0001: ret } // end of method Virt::DoIt

The words public, instance, void, virtual, cil and managed should be self-explanatory.

The hidebysig thing means "hide by signature" (in contrast to "hide by name" semantics which you find in languages like C++). Hidebysig isn't used by the runtime itself, but is used by language compilers and tools to see what has to be hidden (in this case, Do and DoIt will hide methods lower in the class hierarchy which have the same signature, i.d. void <name>() - in this specific case there's nothing to hide because the base class is just System.Object).

On the virtual method Do there's another keyword that might be a bit non-intuitive at first sight, newslot. This keyword is used to indicate that a new virtual method is introduced, which adds a new entry to the vtable. Assume you're overriding the method in some subclass, then there won't be a newslot declaration.

Question: Predict the IL method signatures of all Do methods below:

class Virt2 : Virt { public override void Do() {} } class Virt3 : Virt { public new void Do() {} } class Virt4 : Virt { public new virtual void Do() {} } class Virt5 : Virt { public sealed override void Do() {} }

Another question: On the caller's side (i.e. Virt::Main), which types of calls will be emitted (call or callvirt) for C# code lines 16 and 23? And why? (Note: you might be amazed/confused if you see it ;-)).

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# re: Virtual vs. non-virtual method calls from a perf perspective

Tuesday, February 27, 2007 3:44 AM by joon

Hey Bart, I'm a reader of your blog, but not a highly skilled technical mind. I encountered something strange while running your code.

Run With Debugging:

00:00:15.2517222 (non virtual)

00:00:14.5449290 (virtual)

Run Without Debugging

00:00:08.2957208 (non virtual)

00:00:08.2798889 (virtual)

I know there is a difference between projects that are built for debug, or built for release. Is this related to this performance difference? And to be honest I was surprised. At first I thought there would be a bigger difference, but afterwards I just thought that virtual would be slower and hence MS' decision to not make all methods virtual by default. Care to enlighten me? ;)

# re: Virtual vs. non-virtual method calls from a perf perspective

Tuesday, February 27, 2007 4:39 AM by bart

Hi Joon,

This is pretty strange; indeed, there should be a difference between debug and release; in debug build the figures can be almost the same, but in release build you should observe a difference. Can you check you've built with Release build configuration enabled (assuming you're using Visual Studio)? At the command-line, use the following compilations for comparison:

csc /debug+ virt.cs

csc virt.cs

csc /o virt.cs

You should be able to conclude that virtual is slower indeed. Have fun (a follow-up post will be published later this week - check out the quiz of today as well)!

-Bart