views:

350

answers:

3

I'm wondering how the following code is optimized. Specifically concerning virtual and direct calls. I have commented on how I think everything is optimized but those are just guesses.

public abstract class Super
{
    public abstract void Foo();

    public void FooUser()
    {
        Foo();
    }
}

public class Child1 : Super
{
    public override void Foo()
    {
        //doSomething
    }
}

public class SealedChild : Super
{
    public override void Foo()
    {
        //doSomething
    }
}

class Program
{
    void main()
    {
        Child1 child1 = new Child1();
        child1.Foo(); //Virtual call?
        child1.FooUser(); //Direct call and then a virtual call. 

        SealedChild sealedChild = new SealedChild();
        sealedChild.Foo(); //Direct call?
        sealedChild.FooUser(); 
        /* Two options: either a direct call & then a virtual call
         * Or: direct call with a parameter that has a function pointer to Foo, and then a direct call to foo.
         */

        Super super = child1;
        super.Foo(); //Virtual call.
        super.FooUser(); //Virtual call then direct call.
    }
}
+1  A: 

The compiler doesn't do any type of optimization at all. It always generates an IL 'callvirt' instruction (call virtual). The runtime could in theory remove the virtual part of the call, but every benchmark I've seen and tried indicates this is not the case. Considering even fully static C++ compilers can't do this for trivial situations, it seems unlikely that the JIT is able to pull it off. And even if they could make it work, there's a vast range of much more common performance pitfalls they can spend their time on instead.

Oh, and this blog post by Eric Gunnerson explains why C# always generates callvirt.

Promit
A: 

If it were sealed (maybe edit it?), the compiler or the JIT could issue a non-virtual call when the object is known to be a SealedChild, saving an indirection. Java does this, C# seems not to do it; I don't know what the JIT does though.

Lucero
+2  A: 

In the case where you have a virtual method on a sealed class, and the type of the object reference is the sealed class the virtual call could be avoided. Take the following example. There is no actual reason that GetName needs to be called virtually because we know there can be no sub class of Parent and hence no further virtual dispatch.

sealed class Parent : Child  {
  public override string GetName() { return "foo"; }
}

public void Test() {
  var p = new Parent();
  var name = p.GetName();
}

A compiler could choose to notice this and output a call IL instruction instead of a callvirt. However both the C# and VB.Net Compiler choose not to perform this optimization. Both will emit callvirt.

The JIT is also free to make such an optimization. It also chooses not to.

That does not mean however that you should not seal your classes. Classes should be sealed unless you actually intend for someone to inherit from them. Otherwise you're opening yourself up to scenarios you have failed to accurately cost.

Additionally, there's nothing stopping the compilers and JIT from implementing this at a later date.

JaredPar