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

コンパイラはこれに気づき、callvirt の代わりに call IL 命令を出力することを選択できます。ただし、C# と VB.Net コンパイラの両方が、この最適化を実行しないことを選択します。どちらも callvirt を発行します。

JIT は、このような最適化も自由に行うことができます。また、しないことを選択します。

ただし、クラスを封印してはならないという意味ではありません。実際に誰かがクラスを継承するつもりでない限り、クラスは封印する必要があります。そうしないと、正確にコストを計算できなかったシナリオに自分自身を開いてしまうことになります。

さらに、コンパイラと JIT が後でこれを実装することを妨げるものは何もありません。

于 2009-04-21T00:58:04.670 に答える
5

コンパイラは、どのような種類の最適化もまったく行いません。これは常に IL 'callvirt' 命令 (call virtual) を生成します。ランタイムは理論的には呼び出しの仮想部分を削除できますが、私が見て試したすべてのベンチマークは、そうではないことを示しています。完全に静的な C++ コンパイラでさえ、些細な状況ではこれを行うことができないことを考えると、JIT がそれを実行できる可能性は低いと思われます。そして、仮にそれを機能させることができたとしても、代わりに彼らが時間を費やすことができる、はるかに一般的なパフォーマンスの落とし穴が数多くあります。

ああ、Eric Gunnerson によるこのブログ投稿では、C# が常に callvirt を生成する理由を説明しています。

于 2009-04-21T00:13:14.593 に答える
0

それが封印されていた場合 (おそらく編集?)、オブジェクトが SealedChild であることがわかっている場合、コンパイラまたは JIT非仮想呼び出しを発行し、間接化を回避できます。Java はこれを行いますが、C# はそうではないようです。ただし、JITが何をするのかわかりません。

于 2009-04-21T00:16:22.197 に答える