1

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

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

3 回答 3

6

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

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 在以后实现它。

于 2009-04-21T00:58:04.670 回答
5

编译器根本不做任何类型的优化。它总是生成一个 IL 'callvirt' 指令(调用虚拟)。理论上,运行时可以删除调用的虚拟部分,但我看到并尝试过的每个基准测试都表明情况并非如此。考虑到即使是完全静态的 C++ 编译器也无法在微不足道的情况下做到这一点,JIT 似乎不太可能实现这一目标。即使他们能够让它发挥作用,他们也可以将时间花在大量更常见的性能缺陷上。

哦,Eric Gunnerson 的这篇博文解释了为什么 C# 总是生成 callvirt。

于 2009-04-21T00:13:14.593 回答
0

如果它是密封的(也许是编辑它?),编译器或 JIT可以在已知对象是 SealedChild 时发出非虚拟调用,从而保存间接。Java 会这样做,C# 似乎不会这样做;我不知道 JIT 是做什么的。

于 2009-04-21T00:16:22.197 回答