繁体   English   中英

在C ++中使用接口的性能损失?

[英]Performance penalty for working with interfaces in C++?

在C ++中使用接口(抽象基类)时是否存在运行时性能损失?

简答:没有。

长答案:一个类在其层次结构中具有影响其速度的基类或祖先数量。 唯一的问题是方法调用的成本。

非虚方法调用有成本(但可以内联)
虚拟方法调用的成本稍高,因为您需要在调用之前查找要调用的方法(但这是一个简单的表查找而不是搜索)。 由于接口上的所有方法都是虚拟的,因此存在此成本。

除非您正在编写一些超高速敏感的应用程序,否则这应该不是问题。 使用界面可以获得的额外清晰度通常可以弥补任何感知到的速度降低。

使用虚拟分派调用的函数不会内联

对虚函数有一种惩罚很容易忘记:在对象的类型不知道编译时的(常见)情况下,虚拟调用没有内联。 如果你的函数很小并且适合内联,那么这个代价可能非常大,因为你不仅增加了调用开销,而且编译器也限制了它如何优化调用函数(它必须假设虚函数可能已经更改了一些寄存器或内存位置,它不能在调用者和被调用者之间传播常量值)。

虚拟通话费用取决于平台

至于与正常函数调用相比的调用开销惩罚,答案取决于您的目标平台。 如果您的目标是使用x86 / x64 CPU的PC,则调用虚拟功能的代价非常小,因为现代x86 / x64 CPU可以对间接调用执行分支预测。 但是,如果您的目标是PowerPC或其他一些RISC平台,则虚拟呼叫损失可能非常大,因为在某些平台上从不预测间接呼叫(参见PC / Xbox 360跨平台开发最佳实践 )。

与常规呼叫相比,每个虚拟功能呼叫有一个小的惩罚。 除非您每秒进行数十万次呼叫,否则您不太可能发现差异,并且价格通常值得支付以增加代码清晰度。

当您调用虚函数(例如通过接口)时,程序必须在表中查找函数以查看要为该对象调用的函数。 与直接调用函数相比,这会带来一点点损失。

此外,当您使用虚函数时,编译器无法内联函数调用。 因此,对于某些小功能使用虚函数可能会受到惩罚。 这通常是您可能会看到的最大“性能”。 如果函数很小并且多次调用(例如在循环内),这实际上只是一个问题。

在某些情况下适用的另一种替代方法是使用模板进行编译时多态性。 例如,当您想要在程序开始时进行实现选择,然后在执行期间使用它时,它很有用。 运行时多态性的一个例子

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>();
}

我不认为成本比较是在虚函数调用和直接函数调用之间。 如果您正在考虑使用抽象基类(接口),那么您可能希望根据对象的动态类型执行多个操作之一。 你必须以某种方式作出这种选择。 一种选择是使用虚函数。 另一种是通过RTTI(可能很昂贵)或者将类型()方法添加到基类(可能增加每个对象的内存使用)来切换对象的类型。 因此,虚拟函数调用的成本应与替代成本进行比较,而不是无所事事的成本。

大多数人注意到运行时惩罚,这是正确的。

但是,根据我从事大型项目的经验,清晰的接口和适当的封装带来的好处很快抵消了速度的提高。 可以交换模块化代码以实现改进的实现,因此最终结果是大的增益。

您的里程可能会有所不同,这显然取决于您正在开发的应用程序。

需要注意的一点是虚拟函数调用成本可能因平台而异。 在控制台上,它们可能更明显,因为通常vtable调用意味着缓存未命中并且可以拧紧分支预测。

请注意,多重继承会使用多个vtable指针使对象实例膨胀。 使用x86上的G ++,如果你的类有一个虚方法而没有基类,你有一个指向vtable的指针。 如果你有一个带有虚方法的基类,你仍然有一个指向vtable的指针。 如果您有两个带有虚方法的基类,则每个实例上都有两个 vtable指针。

因此,使用多重继承(这是在C ++中实现接口的原因),您需要在对象实例大小中支付基类时间指针大小。 内存占用的增加可能会产生间接的性能影响。

在C ++中使用抽象基类通常要求使用虚函数表,所有的接口调用都将通过该表进行查找。 与原始函数调用相比,成本很小,因此请确保在担心之前需要比这更快。

我所知道的唯一主要区别是,因为你没有使用具体的类,所以内联更难(很多?)。

我唯一能想到的是,虚拟方法的调用速度比非虚方法要慢一些,因为调用必须通过虚方法表

但是,这是搞砸你的设计的一个坏理由。 如果需要更高性能,请使用速度更快的服务器。

对于包含虚函数的任何类,使用vtable。 显然,通过像vtable这样的调度机制调用方法比直接调用慢,但在大多数情况下,你可以使用它。

是的,但据我所知,没什么值得注意的。 性能上升是因为每次方法调用都有“间接”。

但是,它实际上取决于您使用的编译器,因为某些编译器无法在继承自抽象基类的类中内联方法调用。

如果你想确定你应该运行自己的测试。

是的,有罚款。 可以提高平台性能的一点是使用没有虚函数的非抽象类。 然后使用成员函数指针指向非虚函数。

我知道这是一个不寻常的观点,但即便提到这个问题也让我怀疑你在课堂结构上花了太多心思。 我已经看到很多系统有太多“抽象级别”,而这一点使它们容易出现严重的性能问题,不是由于方法调用的成本,而是由于倾向于进行不必要的调用。 如果这发生在多个层面,那就是杀手锏。 看一看

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM