2

1レベルの単一継承における仮想関数のオーバーヘッドを決定するために(おそらく非常に非科学的な)小さなテストを設定しました。得られた結果は、派生クラスに多態的にアクセスする場合と直接アクセスする場合でまったく同じでした。少し驚いたのは、関数が仮想として宣言されたときに導入される計算時間の大きさでした(以下の結果を参照)。

メンバー関数をそのように宣言するとき、非常に多くのオーバーヘッドがありますか?また、派生クラスに直接アクセスする場合でも、なぜそれがまだ存在するのですか?

コードは次のとおりです。

class base
{
public:
    virtual ~base() {}
    virtual uint func(uint i) = 0;
};

class derived : public base
{
public:
    ~derived() {}
    uint func(uint i) { return i * 2; }
};

uint j = 0;
ulong k = 0;
double l = 0;
ushort numIters = 10;
base* mybase = new derived;  // or derived* myderived = ...

for(ushort i = 0; i < numIters; i++)
{
  clock_t start2, finish2;
  start2 = clock();

  for (uint j = 0; j < 100000000; ++j)
        k += mybase->func(j);

  finish2 = clock();
  l += (double) (finish2 - start2);
  std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl;

}

std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl;
std::cout << "Average duration: " << l / numIters << " ms." << std::endl;

結果:

base* mybase = new derived;平均で約338ミリ秒になります。

derived* myderived = new derived;平均で約338ミリ秒になります。

継承を削除して仮想関数を削除すると、平均で約38ミリ秒になります。

それはほぼ10分の1です!したがって、基本的に、仮想として宣言された関数がある場合、それを多態的に使用しなくても、オーバーヘッドは常に同じように存在しますか?

ありがとう。

4

2 に答える 2

7

「直接」アクセスすることは、「間接的に」アクセスすることと同じ作業を行っています。

で関数を呼び出すと、myderivedそこに格納されているポインターは、から派生したクラスのオブジェクトを指す可能性がありますderived。コンパイラは、それが実際にderivedオブジェクトであると想定することはできません。仮想関数をオーバーライドするさらに派生したクラスのオブジェクトである可能性があるため、この場合と同様に仮想関数のディスパッチが必要mybaseです。どちらの場合も、関数は呼び出される前に仮想関数テーブルで検索されます。

関数を非ポリモーフィックに呼び出すには、ポインターを使用しないでください。

derived myderived;
myderived.func(1); 

仮想関数を削除すると、コンパイラは関数呼び出しをインライン化できるため、基本的に単純なループになります。

for (uint j = 0; j < 100000000; ++j)
    k += i * 2;

これは、100000000 回の関数呼び出しのオーバーヘッドを節約できるため、はるかに高速であり、コンパイラーは、関数呼び出しがあった場合にはない方法でループをさらに最適化することさえできる可能性があります。

関数が実際の作業を行った場合、インライン化されたバージョンと仮想関数呼び出しの違いははるかに小さくなることにも注意してください。この例では、関数本体にほとんど時間がかからないため、関数を呼び出すためのコストが本体を実行するためのコストを上回ります。

于 2010-06-29T11:29:21.947 に答える
2

仮想機能のコストは基本的にゼロです。実際のパフォーマンスの問題のほとんどは、不必要にふさふさしたコール ツリーが、問題だとは決して思いもよらないことを行うことによって引き起こされます。

それらを見つける方法は、デバッガーでアプリを数回一時停止し、呼び出しスタックを含む状態を調べることです。この方法を使用して 43 倍のスピードアップを実現する例を次に示します。

于 2010-06-29T13:32:28.190 に答える