C++ でインターフェイス (抽象基本クラス) を使用すると、実行時のパフォーマンスが低下しますか?
16 に答える
短い答え: いいえ。
長い答え: クラスの速度に影響を与えるのは、クラスの階層内にある基本クラスや先祖の数ではありません。唯一のことは、メソッド呼び出しのコストです。
非仮想メソッド呼び出しにはコストがかかります (ただし、インライン化できます)
仮想メソッド呼び出しは、呼び出す前に呼び出すメソッドをルックアップする必要があるため、コストがわずかに高くなります (ただし、これは単純なテーブル ルックアップであり、検索ではありません)。 . インターフェイス上のすべてのメソッドは定義上仮想であるため、このコストが発生します。
非常に速度に敏感なアプリケーションを作成していない限り、これは問題になりません。インターフェースを使用することで得られる特別な明快さは、通常、認識された速度の低下を補います.
仮想ディスパッチを使用して呼び出される関数はインライン化されません
仮想関数には、忘れがちな一種のペナルティがあります。オブジェクトの型がコンパイル時にわからない (一般的な) 状況では、仮想呼び出しはインライン化されません。関数が小さく、インライン化に適している場合、呼び出しのオーバーヘッドが追加されるだけでなく、コンパイラが呼び出し関数を最適化する方法も制限されるため、このペナルティは非常に重要になる可能性があります (仮想関数が一部のレジスタまたはメモリ位置が変更された場合、呼び出し元と呼び出し先の間で定数値を伝達できません)。
仮想通話料金はプラットフォームによって異なります
通常の関数呼び出しと比較した呼び出しオーバーヘッドのペナルティについては、答えはターゲット プラットフォームによって異なります。x86/x64 CPU を搭載した PC をターゲットにしている場合、最新の x86/x64 CPU は間接呼び出しで分岐予測を実行できるため、仮想関数を呼び出すことによるペナルティは非常に小さくなります。ただし、PowerPC またはその他の RISC プラットフォームをターゲットにしている場合、一部のプラットフォームでは間接呼び出しがまったく予測されないため、仮想呼び出しのペナルティが非常に大きくなる可能性があります ( PC/Xbox 360 クロス プラットフォーム開発のベスト プラクティスを参照)。
通常の呼び出しと比較して、仮想関数呼び出しごとにわずかなペナルティがあります。1 秒あたり数十万回の呼び出しを行わない限り、違いを観察することはまずありません。
仮想関数を呼び出す場合 (たとえば、インターフェイスを介して)、プログラムはテーブル内の関数を検索して、そのオブジェクトに対してどの関数を呼び出すかを確認する必要があります。これは、関数への直接呼び出しと比較して小さなペナルティを与えます。
また、仮想関数を使用する場合、コンパイラは関数呼び出しをインライン化できません。したがって、一部の小さな関数に仮想関数を使用すると、ペナルティが発生する可能性があります。これは一般に、最も大きなパフォーマンス「ヒット」です。これは、関数が小さく、ループ内からなど、何度も呼び出される場合にのみ問題になります。
場合によっては適用可能な別の代替手段は、テンプレートを使用したコンパイル時のポリモーフィズムです。たとえば、プログラムの開始時に実装の選択を行い、実行中にそれを使用する場合に便利です。実行時ポリモーフィズムの例
class AbstractAlgo
{
virtual int func();
};
class Algo1 : public AbstractAlgo
{
virtual int func();
};
class Algo2 : public AbstractAlgo
{
virtual int func();
};
void compute(AbstractAlgo* algo)
{
// Use algo many times, paying virtual function cost each time
}
int main()
{
int which;
AbstractAlgo* algo;
// read which from config file
if (which == 1)
algo = new Algo1();
else
algo = new Algo2();
compute(algo);
}
コンパイル時のポリモーフィズムを使用して同じ
class Algo1
{
int func();
};
class Algo2
{
int func();
};
template<class ALGO> void compute()
{
ALGO algo;
// Use algo many times. No virtual function cost, and func() may be inlined.
}
int main()
{
int which;
// read which from config file
if (which == 1)
compute<Algo1>();
else
compute<Algo2>();
}
多重継承は、複数の vtable ポインターでオブジェクト インスタンスを膨張させることに注意してください。x86 の G++ では、クラスに仮想メソッドがあり、基本クラスがない場合、vtable へのポインターは 1 つになります。仮想メソッドを持つ基本クラスが 1 つある場合でも、vtable へのポインターは 1 つです。仮想メソッドを持つ 2 つの基本クラスがある場合、各インスタンスに2 つのvtable ポインターがあります。
したがって、多重継承 (C++ でのインターフェイスの実装とは) を使用すると、基本クラスにポインター サイズを掛けて、オブジェクト インスタンスのサイズを計算することになります。メモリ フットプリントの増加は、パフォーマンスに間接的な影響を与える可能性があります。
コストの比較は、仮想関数呼び出しと単純な関数呼び出しの間ではないと思います。抽象基本クラス (インターフェース) の使用を検討している場合、オブジェクトの動的タイプに基づいていくつかのアクションのうちの 1 つを実行したい状況があります。どういうわけかその選択をしなければなりません。1 つのオプションは、仮想関数を使用することです。もう 1 つは、RTTI (コストがかかる可能性がある) を使用するか、基本クラスに type() メソッドを追加する (各オブジェクトのメモリ使用量が増加する可能性がある)、オブジェクトの型を切り替えることです。したがって、仮想関数呼び出しのコストは、何もしない場合のコストではなく、代替手段のコストと比較する必要があります。
ほとんどの人は実行時のペナルティに気付きますが、それは当然のことです。
ただし、大規模なプロジェクトに携わった私の経験では、明確なインターフェイスと適切なカプセル化による利点は、速度の向上をすぐに相殺します。モジュラー コードは実装を改善するために交換できるため、最終的には大きな利益が得られます。
走行距離は異なる場合があり、開発しているアプリケーションによって明らかに異なります。
注意すべきことの 1 つは、仮想関数呼び出しのコストがプラットフォームごとに異なる可能性があることです。コンソールでは、通常、vtable 呼び出しはキャッシュ ミスを意味し、分岐予測を台無しにする可能性があるため、より目立つ場合があります。
珍しい見方だとは思いますが、この問題に触れても、クラスの構造を考えすぎているのではないかと思います。「抽象化のレベル」が多すぎるシステムをたくさん見てきましたが、それだけでは、メソッド呼び出しのコストではなく、不要な呼び出しを行う傾向があるため、深刻なパフォーマンスの問題が発生しやすくなりました。これが複数のレベルで発生する場合、それはキラーです。見てください
はい、ペナルティがあります。仮想関数のない非抽象クラスを使用すると、プラットフォームのパフォーマンスが向上する可能性があります。次に、非仮想関数へのメンバー関数ポインターを使用します。
C++ で抽象基本クラスを使用すると、通常、仮想関数テーブルの使用が義務付けられます。すべてのインターフェイス呼び出しは、そのテーブルを介して検索されます。生の関数呼び出しに比べてコストは小さいので、心配する前にそれよりも速くする必要があることを確認してください。
私が知っている唯一の主な違いは、具体的なクラスを使用していないため、インライン展開が (はるかに?) 難しいということです。
私が考えることができる唯一のことは、呼び出しが仮想メソッドテーブルを通過する必要があるため、仮想メソッドは非仮想メソッドよりも呼び出しが少し遅いということです。
ただし、これは設計を台無しにする悪い理由です。より高いパフォーマンスが必要な場合は、より高速なサーバーを使用してください。
仮想関数を含むクラスについては、vtable が使用されます。明らかに、vtable のようなディスパッチ メカニズムを介してメソッドを呼び出すと、直接呼び出しよりも時間がかかりますが、ほとんどの場合はそれで問題ありません。
はい、しかし私の知る限り特筆すべきことは何もありません。パフォーマンスの低下は、各メソッド呼び出しにある「間接化」が原因です。
ただし、一部のコンパイラは抽象基本クラスから継承するクラス内のメソッド呼び出しをインライン化できないため、実際には使用しているコンパイラに依存します。
確実にしたい場合は、独自のテストを実行する必要があります。