ループ内で仮想関数を 1000 回呼び出すと、vtable ルックアップのオーバーヘッドが 1000 回発生するのでしょうか、それとも 1 回だけでしょうか?
7 に答える
コンパイラはそれを最適化できる場合があります。たとえば、次の例は (少なくとも概念的には) 簡単に最適化できます。
Foo * f = new Foo;
for ( int i = 0; i < 1000; i++ ) {
f->func();
}
ただし、他のケースはより困難です。
vector <Foo *> v;
// populate v with 1000 Foo (not derived) objects
for ( int i = 0; i < v.size(); i++ ) {
v[i]->func();
}
同じ概念の最適化が適用できますが、コンパイラーが認識するのははるかに困難です。
結論 - 本当に気にするなら、すべての最適化を有効にしてコードをコンパイルし、コンパイラのアセンブラ出力を調べてください。
Visual C++ コンパイラ (少なくとも VS 2008 まで) は、vtable ルックアップをキャッシュしません。さらに興味深いことに、オブジェクトの静的型が seal である仮想メソッドへの呼び出しを直接ディスパッチしません。ただし、仮想ディスパッチ ルックアップの実際のオーバーヘッドは、ほとんどの場合無視できます。ときどきヒットするのは、マネージド VM のように C++ の仮想呼び出しを直接呼び出しに置き換えることができないという事実です。これは、仮想呼び出しのインライン展開がないことも意味します。
アプリケーションへの影響を確認する唯一の真の方法は、プロファイラーを使用することです。
元の質問の詳細について: 呼び出している仮想メソッドが、仮想ディスパッチ自体が測定可能なパフォーマンスの影響を被るほど些細なものである場合、そのメソッドは十分に小さいため、vtable はループ全体でプロセッサのキャッシュに残ります。vtable から関数ポインターをプルするアセンブリ命令が 1000 回実行されても、パフォーマンスへの影響は(1000 * time to load vtable from system memory)
.
仮想関数を呼び出しているオブジェクトが変更されないとコンパイラが推測できる場合、理論的には、ループから vtable ルックアップを引き上げることができるはずです。
特定のコンパイラが実際にこれを行うかどうかは、それが生成するアセンブリ コードを調べることによってのみ確認できます。
特にキャッシュに必要なすべての値があるループでは非常に高速な操作であるため、問題は vtable ルックアップではないと思います (ループが複雑すぎない場合、複雑な場合、仮想関数はパフォーマンスに大きな影響を与えません)。 . 問題は、コンパイラがコンパイル時にその関数をインライン化できないという事実です。
これは、仮想関数が非常に小さい場合 (たとえば、1 つの値のみを返す場合) に特に問題になります。値を返すだけの関数呼び出しが必要なため、この場合の相対的なパフォーマンスへの影響は非常に大きくなる可能性があります。この関数をインライン化できれば、パフォーマンスが大幅に向上します。
仮想関数がパフォーマンスを消費する場合、vtable はあまり気にしません。
これは、コンパイラとループの外観に依存すると思います。最適化コンパイラは多くのことを行うことができ、VF 呼び出しが予測可能であれば、コンパイラが役立ちます。コンパイラのドキュメントで、コンパイラが行う最適化について何かを見つけることができるかもしれません。