编译器如何优化由密封类实现的虚拟方法

时间:2009-04-20 23:59:58

标签: c# virtual abstract sealed optimization

我想知道以下代码是如何优化的。特别涉及虚拟和直接呼叫。我评论过我认为一切都是优化的,但这些只是猜测。

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

3 个答案:

答案 0 :(得分:5)

编译器根本不进行任何类型的优化。它总是生成IL'callvirt'指令(调用虚拟)。从理论上讲,运行时可以删除调用的虚拟部分,但我见过并尝试过的每个基准测试都表明情况并非如此。考虑到即使是完全静态的C ++编译器也无法在琐碎的情况下执行此操作,JIT似乎不太可能将其拉下来。即使他们能够使其发挥作用,也会有大量更常见的性能陷阱,而不是花时间。

哦,this blog post by Eric Gunnerson解释了为什么C#总是生成callvirt。

答案 1 :(得分:5)

如果您在密封类上有虚方法,并且对象引用的类型是密封类,则可以避免虚拟调用。以下面的例子为例。没有实际的原因需要虚拟调用GetName,因为我们知道没有父类的子类,因此没有进一步的虚拟调度。

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

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

编译器可以选择注意这一点并输出调用IL指令而不是callvirt。但是,C#和VB.Net编译器都选择不执行此优化。两者都会发出callvirt。

JIT也可以自由地进行这样的优化。它也选择不这样做。

但这并不意味着你不应该密封你的课程。除非你真的打算让别人继承,否则应该密封课程。否则,您将打开自己未能准确降低成本的方案。

此外,没有什么能阻止编译器和JIT在以后实现这一点。

答案 2 :(得分:0)

如果密封(可能编辑?),当已知对象为SealedChild时,编译器或JIT 可以发出非虚拟调用,从而保存间接。 Java这样做,C#似乎没有这样做;我不知道JIT做了什么。