3

と の 2 つの仮想メンバー関数を持つクラスがfooありwrapperます。foo短くて高速で、何度wrapperも呼び出すループが含まれています。オブジェクトへのポインターから呼び出された場合でも、ラッパー関数内fooへの呼び出しをインライン化する方法があることを願っています。foo

MyClass *obj = getObject();
obj->foo(); // As I understand it, this cannot be inlined. That's okay.
obj->wrapper(); // Nor will this. However, I hope that the machine code
                // for the wrapper function will contain inlined calls to
                // foo().

基本的に、コンパイラーが複数のバージョンのラッパー関数 (可能なクラスごとに 1 つ) を生成し、適切な へのインライン呼び出しを生成するようにします。これは、実行する関数をfoo選択する前にオブジェクトの種類が決定されるため可能になるはずです。wrapperこれは可能ですか?この最適化をサポートするコンパイラはありますか?

編集:これまでのすべてのフィードバックと回答に感謝しており、そのうちの 1 つを選ぶことになるかもしれません。ただし、ほとんどの回答は、この最適化が実現可能であると考える理由を説明する私の質問の最後の部分を無視しています。それが本当に私の質問の核心であり、誰かがそれに対処できることを望んでいます。

編集2:Vladの答えを選んだのは、彼が人気のある回避策を提案し、提案された最適化に部分的に対処したためです(Davidの答えのコメントで)。回答を書いてくれたすべての人に感謝します-私はそれらすべてを読みましたが、明確な「勝者」はありませんでした.

また、私が提案しているものと非常によく似た最適化を提案している学術論文を見つけました: http://www.ebb.org/bkuhn/articles/cpp-opt.pdf

4

5 に答える 5

3

場合によっては、コンパイラーはコンパイル時に仮想ディスパッチの動作を判別し、非仮想関数の呼び出しを実行したり、関数をインライン化したりすることができます。これは、クラスが継承チェーンの「トップ」であるか、これら2つの関数がオーバーロードされていないことがわかる場合にのみ実行できます。多くの場合、これは単純に不可能です。特に、プログラム全体でレイトタイム最適化を有効にしていない場合はなおさらです。

コンパイラの最適化の結果を確認したい場合を除いて、最善の策は、内部ループで仮想関数をまったく使用しないことです。たとえば、次のようなものです。

class Foo {
  public:
    virtual void foo()
    {
        foo_impl();
    }

    virtual void bar()
    {
        for (int i = 0; i < ∞; ++i) {
            foo_impl();
        }
    }

  private:
    void foo_impl() { /* do some nasty stuff here */ }
};

しかし、その場合、誰かが入ってクラスから継承し、「バー」によって呼び出される「foo」の独自の実装を投入する可能性があるという考えを明確に放棄します。基本的に、両方を再実装する必要があります。

一方で、それは時期尚早の最適化のようなにおいがします。最近のCPUは、メソッドが仮想的に仮想である場合でも、ループを「ロック」し、ループからの終了を予測して、同じµOPを繰り返し実行する可能性があります。したがって、最適化に時間を費やす前に、これがボトルネックであると慎重に判断することをお勧めします。

于 2012-10-18T21:11:34.440 に答える
2

次の階層を想像してください。

class Base
{
    virtual void foo();
    virtual void wrapper();
};

class Derived1: public Base
{
    virtual void foo() { cout << "Derived1::foo"; }
    virtual void wrapper() { foo(); }
};

class Derived2: public Derived1
{
    virtual void foo() { cout << "Derived2::foo"; }
};

Base * p1 = new Derived1;
p1->wrapper();  // calls Derived1::wrapper which calls Derived1::foo
Base * p2 = new Derived2;
p2->wrapper();  // calls Derived1::wrapper which calls Derived2::foo

問題が見えますか?Derived1::wrapperはDerived2::foo を呼び出す必要があります。Derived1::foo と Derived2::foo のどちらを呼び出すかは実行時までわからないため、インライン化する方法はありません。

インライン化が可能であることを確認したい場合は、インライン化したい関数が仮想でないことを確認してください。fooあなたの説明から、階層内のすべてのクラスが と の両方を再実装する場合、これが可能になる可能性があるようwrapperです。関数は、オーバーライドするために仮想である必要はありません。

于 2012-10-18T21:19:58.980 に答える
2

いいえ、関数呼び出しは、ポインターまたは参照 (thisポインターを含む) を介して実行された場合、インライン化されません。foo現在の型を拡張し、オーバーライドせずにオーバーライドする新しい型を作成できますwrapper

コンパイラが関数をインライン化できるようにする場合は、その呼び出しの仮想ディスパッチを無効にする必要があります。

void Type::wrapper() {
    Type::foo();            // no dynamic dispatch
}
于 2012-10-18T21:16:40.460 に答える
1

これは正確ではありません。virtual関数はインライン化できますが、コンパイラがオブジェクトの静的型を確実に知っている場合に限ります。したがって、ポリモーフィズムが機能することが保証されます。

例えば:

struct A
{
    virtual void foo()
    {
    }
};
struct B
{
    virtual void foo()
    {
    }
};

int main()
{
    A a;
    a.foo(); //this can be inlined
    A* pa = new A;
    pa->foo(); //so can this
}

void goo(A* pa)
{
    pa->foo() //this probably can't
}

そうは言っても、あなたの場合、これは起こり得ないようです。できることはvirtual、実際に機能を実装して静的に呼び出す別の非関数を使用することです。これにより、呼び出しはコンパイル時に解決されます。

class MyClass
{
    virtual void foo() = 0;
    virtual void wrapper() = 0;
};

class Derived : MyClass
{
    void fooImpl()
    {
       //keep the actual implementation here
    }
    virtual void foo()
    {
       fooImpl();
    }
    virtual void wrapper()
    {
       for ( int i = 0 ; i < manyTimes ; i++ )
          fooImpl(); //this can get inlined
    }
};

または単にDerived::foo()@avakarによって指摘されたように。

于 2012-10-18T21:08:52.013 に答える
0

あなたの関数はvirtualあり、その型は実行時まで示されないので、コンパイラが何らかのクラスのコードをそれにインライン化することをどのように期待しますか?

いくつかの同様の状況で、私は2つの機能を持っていました:fooそれは仮想であり、foo_implそれは通常の機能であり、から呼び出されfooますwrapper

于 2012-10-18T21:09:53.913 に答える