C++でインターフェイス(抽象基本クラス)を使用すると、実行時のパフォーマンスが低下しますか?
短い答え:いいえ。
長い答え:速度に影響を与えるのは、基本クラスや、クラスが階層内に持つ祖先の数ではありません。唯一のことは、メソッド呼び出しのコストです。
非仮想メソッド呼び出しにはコストがかかります(ただし、インライン化できます)
仮想メソッド呼び出しは、呼び出す前に呼び出すメソッドを検索する必要があるため、コストが少し高くなります(ただし、これは単純なテーブルルックアップです)。 ない 検索)。インターフェイス上のすべてのメソッドは定義上仮想であるため、このコストがかかります。
速度に敏感なアプリケーションを作成しているのでない限り、これは問題にはなりません。インターフェイスを使用することで得られる追加の明快さは、通常、知覚される速度の低下を補います。
忘れがちな仮想関数には1種類のペナルティがあります。オブジェクトのタイプがコンパイル時間を知らない(一般的な)状況では、仮想呼び出しはインライン化されません。関数が小さく、インライン化に適している場合、呼び出しのオーバーヘッドを追加するだけでなく、コンパイラーは呼び出し元の関数を最適化する方法にも制限があるため、このペナルティは非常に重要になる可能性があります(仮想関数が一部のレジスタまたはメモリ位置を変更した場合、呼び出し元と呼び出し先の間で定数値を伝播できません)。
通常の関数呼び出しと比較した呼び出しオーバーヘッドのペナルティについては、答えはターゲットプラットフォームによって異なります。 x86/x64 CPUを搭載したPCをターゲットにしている場合、最新のx86/x64 CPUは間接呼び出しで分岐予測を実行できるため、仮想関数を呼び出すことによるペナルティは非常に小さくなります。ただし、PowerPCまたはその他のRISCプラットフォームをターゲットにしている場合、一部のプラットフォームでは間接通話が予測されないため、仮想通話のペナルティが非常に大きくなる可能性があります(Cf. 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>();
}
ほとんどの人は実行時のペナルティに注意します、そして当然そうです。
ただし、大規模なプロジェクトに取り組んだ私の経験では、明確なインターフェイスと適切なカプセル化のメリットにより、速度の向上がすぐに相殺されました。モジュラーコードは、実装を改善するために交換できるため、最終的な結果は大きな利益になります。
マイレージは異なる場合があり、開発しているアプリケーションによって明らかに異なります。
コストの比較は、仮想関数呼び出しと直接関数呼び出しの間ではないと思います。抽象基本クラス(インターフェース)の使用を検討している場合、オブジェクトの動的タイプに基づいていくつかのアクションの1つを実行したい状況があります。あなたはどういうわけかその選択をしなければなりません。 1つのオプションは、仮想関数を使用することです。もう1つは、RTTI(高額になる可能性がある)またはtype()メソッドを基本クラスに追加する(各オブジェクトのメモリ使用量を増やす可能性がある)ことによる、オブジェクトのタイプの切り替えです。したがって、仮想関数呼び出しのコストは、何もしない場合のコストではなく、代替のコストと比較する必要があります。
多重継承は、複数のvtableポインターでオブジェクトインスタンスを肥大化させることに注意してください。 x86上のG ++では、クラスに仮想メソッドがあり、基本クラスがない場合、vtableへのポインターが1つあります。仮想メソッドを持つ基本クラスが1つある場合でも、vtableへのポインターは1つあります。仮想メソッドを持つ2つの基本クラスがある場合、two vtableポインター各インスタンス上があります。
したがって、多重継承(C++でのインターフェースの実装とは)では、基本クラスにオブジェクトインスタンスサイズのポインターサイズを掛けたものを支払います。メモリフットプリントの増加は、パフォーマンスに間接的な影響を与える可能性があります。
注意すべき点の1つは、仮想関数呼び出しのコストはプラットフォームごとに異なる可能性があるということです。通常、vtable呼び出しはキャッシュミスを意味し、分岐予測を台無しにする可能性があるため、コンソールではそれらがより目立つ場合があります。
珍しい見方だとは思いますが、この問題に触れても、階級構造を考えすぎているのではないかと思います。 「抽象化のレベル」が多すぎるシステムをたくさん見てきましたが、それだけでは、メソッド呼び出しのコストではなく、不要な呼び出しを行う傾向があるため、深刻なパフォーマンスの問題が発生しやすくなりました。これが複数のレベルで発生する場合、それはキラーです。 見てください
はい、ペナルティがあります。プラットフォームのパフォーマンスを向上させる可能性のあるものは、仮想関数のない非抽象クラスを使用することです。次に、非仮想関数へのメンバー関数ポインターを使用します。
C++で抽象基本クラスを使用すると、通常、仮想関数テーブルの使用が義務付けられ、すべてのインターフェイス呼び出しはそのテーブルを介して検索されます。生の関数呼び出しに比べてコストは小さいので、心配する前にそれよりも速く進む必要があることを確認してください。
私が知っている唯一の主な違いは、具象クラスを使用していないため、インライン化が(はるかに?)難しいということです。
私が考えることができる唯一のことは、呼び出しが 仮想メソッドテーブル を経由する必要があるため、仮想メソッドの呼び出しは非仮想メソッドよりも少し遅いということです。
しかし、これはあなたのデザインを台無しにする悪い理由です。より高いパフォーマンスが必要な場合は、より高速なサーバーを使用してください。
仮想関数を含むクラスについては、vtableが使用されます。明らかに、vtableのようなディスパッチメカニズムを介してメソッドを呼び出すことは、直接呼び出すよりも時間がかかりますが、ほとんどの場合、それを使用できます。
はい、しかし私の知る限り注目に値するものは何もありません。パフォーマンスへの影響は、各メソッド呼び出しにある「間接参照」が原因です。
ただし、一部のコンパイラは抽象基本クラスから継承するクラス内でメソッド呼び出しをインライン化できないため、実際には使用しているコンパイラによって異なります。
自分でテストを実行する必要があることを確認したい場合。