在C ++中使用接口的性能损失?
在C ++中使用接口(抽象基类)时会有运行时性能损失吗?
简答:不。
长答案:它不是基类或一个类的祖先的数量在它的层次结构,影响它的速度。 唯一的是方法调用的成本。
非虚拟方法调用有成本(但可以内联)
虚拟方法调用的成本稍高一些,因为在调用之前需要查找调用的方法(但这是一个简单的表, 而不是查找)。 由于界面上的所有方法都是虚拟的,所以这个成本是有的。
除非你正在写一些超高速敏感的应用程序,这应该不成问题。 通过使用界面可以获得额外的清晰度,通常可以弥补速度的下降。
使用虚拟调度调用的函数不是内联的
对于虚拟函数有一种惩罚很容易被忽略:虚拟调用在对象types不知道编译时间的(普通)情况下没有被内联。 如果你的函数很小,适合内联,这个惩罚可能是非常重要的,因为你不仅增加了一个调用开销,而且编译器也限制了如何优化调用函数(它必须假设虚函数可能已经改变了一些寄存器或内存位置,它不能在调用者和被调用者之间传播常量值)。
虚拟通话费用取决于平台
至于与正常的函数调用相比的调用开销惩罚,答案取决于您的目标平台。 如果你的目标是使用x86 / x64 CPU的PC,那么调用虚拟函数的代价是非常小的,因为现代x86 / x64 CPU可以对间接调用执行分支预测。 但是,如果您针对的是PowerPC或其他RISC平台,则虚拟呼叫惩罚可能相当重要,因为间接呼叫在某些平台上从未预测过(参见PC / Xbox 360跨平台开发最佳实践 )。
与普通的调用相比,每个虚函数调用都有一个小的惩罚。 除非您每秒钟处理数十万次通话,否则您不太可能观察到差异,无论如何,这个价格往往值得付出代价。
当你调用一个虚函数(通过一个接口)时,程序必须在表中查找函数,看看哪个函数要调用这个对象。 与直接调用函数相比,这会带来很小的惩罚。
而且,当你使用虚拟函数时,编译器不能内联函数调用。 因此,对于一些小函数使用虚函数可能会受到惩罚。 这通常是你可能会看到的最大的performance“打击”。 这只是一个问题,如果函数很小,并且多次调用,从循环内部说。
另一种适用于某些情况的替代方法是使用模板的编译时多态性。 例如,当您想在程序开始时进行实现select,然后在执行期间使用它,这是非常有用的。 运行时多态的一个例子
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>(); }
我不认为虚拟函数调用和直接函数调用之间的成本比较。 如果您正在考虑使用抽象基类(接口),那么您就有一种情况需要根据对象的dynamictypes执行多个操作之一。 你必须以某种方式做出select。 一种select是使用虚拟function。 另一个是通过RTTI(可能是昂贵的)或者向基类添加type()方法(可能会增加每个对象的内存使用)来切换对象的types。 所以虚拟函数调用的代价应该与替代的代价相比,而不是无所事事的代价。
大多数人注意到运行时间的惩罚,正确的。
然而,根据我从事大型项目的经验,清晰的接口和适当的封装带来的好处很快就抵消了速度的提高。 模块化的代码可以交换改进的实现,所以最终的结果是一个很大的收益。
你的里程可能会有所不同,这显然取决于你正在开发的应用程序。
有一点需要注意的是虚拟函数调用的代价可能因平台而异。 在游戏机上,它们可能更加引人注目,因为通常vtable调用意味着caching未命中,并且可以使分支预测紧缩。
请注意,多重inheritance使用多个vtable指针膨胀对象实例。 使用x86上的G ++,如果你的类有一个虚拟的方法,没有基类,你有一个指向vtable的指针。 如果你有一个虚拟方法的基类,你仍然有一个指向vtable的指针。 如果您有两个使用虚拟方法的基类,则每个实例上都有两个 vtable指针。
因此,通过多重inheritance(这就是在C ++中实现的接口),你需要在基类中乘以对象实例大小的指针大小。 内存占用的增加可能会产生间接的性能影响。
在C ++中使用抽象基类通常要求使用虚函数表,所有的接口调用都将通过该表查找。 与原始函数调用相比,成本是微不足道的,所以确保在担心之前需要比以前更快。
我所知道的唯一的主要区别在于,因为你没有使用具体的类,内联(很难)很难做到。
我唯一能想到的是虚拟方法比非虚拟方法稍慢,因为调用必须通过虚拟方法表 。
但是,这是搞砸你的devise的一个不好的理由。 如果您需要更多性能,请使用更快的服务器。
对于任何包含虚函数的类,都使用vtable。 显然,通过像vtable这样的调度机制调用一个方法比直接调用要慢,但是在大多数情况下你可以忍受。
是的,但是我的知识没有什么值得注意的。 性能打击是因为你在每个方法调用中都有“间接”。
但是,这实际上取决于你使用的编译器,因为一些编译器不能在从抽象基类inheritance的类中内联方法调用。
如果你想确定你应该运行自己的testing。
是的,有一个罚款。 一些可以提高你的平台性能的东西是使用一个没有虚函数的非抽象类。 然后使用一个成员函数指针指向你的非虚函数。
我知道这是一个不常见的观点,但即使提到这个问题,也让我怀疑你是在对class级结构进行太多的思考。 我看到很多系统的抽象层次太多,单靠这些系统就容易出现严重的性能问题,不是由于方法调用的成本,而是由于倾向于进行不必要的调用。 如果这发生在多个层面上,这是一个杀手。 看一看