質問

(おそらく非常に非科学的な)小さなテストを設定して、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倍少ないです!したがって、基本的に、どちらの関数が仮想であると宣言されている場合、私がそれを多型で使用しなくても、オーバーヘッドは常に同じように存在しますか?

ありがとう。

役に立ちましたか?

解決

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

関数をオンにするとき myderived, 、そこに保存されているポインターは、から派生したいくつかのクラスのオブジェクトを指すことができます derived. 。コンパイラは、それが本当にあると仮定することはできません derived オブジェクト、それは仮想関数をオーバーライドするさらなる導出されたクラスのオブジェクトであるかもしれないので、のように仮想関数ディスパッチが必要です mybase 場合。どちらの場合も、関数は呼び出される前に仮想関数テーブルで検索されます。

関数を非政治的に呼び出すには、ポインターを使用しないでください。

derived myderived;
myderived.func(1); 

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

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

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

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

他のヒント

仮想関数は本質的にコストはありません。ほとんどの実際のパフォーマンスの問題は、問題になると推測しないことを不必要にふさふさしたコールツリーによって引き起こされます。

私がそれらを見つける方法は、デバッガーの下でアプリを数回一時停止し、コールスタックを含む状態を調べることです。 これが例です その方法を使用して43xスピードアップを取得します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top