【问题标题】:C++: fighting polymorphism overheadC++:对抗多态性开销
【发布时间】:2011-05-05 22:09:39
【问题描述】:

我知道多态性会增加显着的开销。调用虚函数比调用非虚函数慢。 (我所有的经验都是关于 GCC,但我认为/听说这适用于任何 realcompiler。)

给定的虚函数在同一个对象上多次被调用;我知道对象类型不会改变,而且大多数时候编译器可以很容易地推断出来:

BaseType &obj = ...;
while( looping )
    obj.f(); // BaseType::f is virtual

为了加快代码速度,我可以像这样重写上面的代码:

BaseType &obj = ...;
FinalType &fo = dynamic_cast< FinalType& >( obj );
while( looping )
    fo.f(); // FinalType::f is not virtual

我想知道在这些情况下,由于多态性,避免这种开销的最佳方法是什么。

大写的想法(如第二个 sn-p 所示)对我来说看起来不太好:BaseType 可以被许多类继承,并且尝试对所有类进行大写是相当冗长。

另一个想法可能是将obj.f 存储在函数指针中(没有对此进行测试,不确定它是否会杀死运行时开销),但是这种方法看起来并不完美:与上述方法一样,它需要编写更多代码并且它无法利用一些优化(例如:如果FinalType::f 是一个内联函数,它不会被内联 - 但我想避免这种情况的唯一方法是将obj 转换为最终类型...)

那么,有没有更好的方法呢?

编辑: 好吧,当然这不会产生太大影响。这个问题主要是想知道是否有事可做,因为看起来这个开销是免费提供的(这个开销看起来很容易杀死)我不明白为什么不这样做。

一个用于小优化的简单关键字,例如 C99 restrict,告诉编译器多态对象是固定类型,这是我所希望的。

无论如何,只是为了回答 cmets,存在一点开销。看看这个临时 extreme 代码:

struct Base { virtual void f(){} };
struct Final : public Base { void f(){} };

int main( ) {
    Final final;
    Final &f = final;
    Base &b = f;

    for( int i = 0; i < 1024*1024*1024; ++ i )
#ifdef BASE
        b.f( );
#else
        f.f( );
#endif

    return 0;
}

编译和运行它,花费时间:

$ for OPT in {"",-O0,-O1,-O2,-O3,-Os}; do
    for DEF in {BASE,FINAL}; do
        g++ $OPT -D$DEF -o virt virt.cpp &&
        TIME="$DEF $OPT: %U" time ./virt;
    done;
  done           
BASE : 5.19                                                                                                                                                                         
FINAL : 4.21                                                                                                                                                                        
BASE -O0: 5.22                                                                                                                                                                      
FINAL -O0: 4.19                                                                                                                                                                     
BASE -O1: 3.55                                                                                                                                                                      
FINAL -O1: 1.53                                                                                                                                                                     
BASE -O2: 3.61                                                                                                                                                                      
FINAL -O2: 0.00                                                                                                                                                                     
BASE -O3: 3.58                                                                                                                                                                      
FINAL -O3: 0.00                                                                                                                                                                     
BASE -Os: 6.14                                                                                                                                                                      
FINAL -Os: 0.00

我猜只有 -O2、-O3 和 -Os 是内联 Final::f

这些测试已经在我的机器上运行,运行最新的 GCC 和 AMD Athlon(tm) 64 X2 Dual Core Processor 4000+ CPU。我想在更便宜的平台上它可能会慢很多。

【问题讨论】:

  • 所以,我认为您是在说您的代码爬行速度很慢,而您对其进行了分析,发现问题出在多态性上?
  • 如果fBaseType 中是虚拟的并且FinalType 是从BaseType 派生的,那么fFinalType 中也是虚拟的。
  • 还有。 dynamic_cast&lt;&gt;() 有运行时检查的代价,多态的代价是单指针解引用。我建议每当您说“开销”这个词时,请确保您确切地说出该开销是什么,至少在您第一次谈论该开销时。只是为了让我们清楚我们要在这里消除什么。所以,现在,我认为你分析了这两种方法,发现多态性比你的 hack 慢?
  • @peoro:如果FinalType::f的参数类型与BaseType::f的参数类型相同,那么FinalType::f是虚拟的,会覆盖BaseType::f。在派生类中是否使用关键字virtual 声明函数并不重要。在您的示例中,由于 f 没有参数,因此 FinalType::f 会覆盖 BaseType::f
  • 即使您知道 FinalType 始终是派生最多的类,编译器通常也很难或不可能知道这一点。虽然编译器可以优化一些虚函数调用并确定在编译时调用哪个函数,但在大多数情况下它不能。您在单个翻译单元中声明、定义和使用所有类的示例测试是编译器何时可以进行此优化的罕见示例。

标签: c++ optimization polymorphism virtual-functions


【解决方案1】:

如果动态调度是你程序的性能瓶颈,那么解决问题的方法就是不使用动态调度(不要使用虚函数)。

您可以通过使用模板和泛型编程而不是虚函数来将一些运行时多态性替换为编译时多态性。这可能会或可能不会导致更好的性能;只有分析员才能确定地告诉你。

但要明确的是,正如 wilhelmtell 已经在 cmets 中针对该问题指出的那样,动态调度引起的开销很少足以担心。在您用笨重的自定义实现替换内置的便利性之前,请绝对确保它是您的性能热点。

【讨论】:

  • 有时你被“强迫”使用多态性(因此动态调度,直到现在我都错过了这个词)。当然,您可以对多态指针/引用进行大写,然后使用它(将其传递给模板函数或其他);这就是我对上铸解决方案的想法。正如在编辑原始帖子后所说,这主要是对事情如何运作的怀疑。谢谢你的回答,顺便说一句。
【解决方案2】:

如果你需要使用多态,那就使用它。真的没有比这更快的方法了。

但是,我会回答另一个问题:这是您最大的问题吗?如果是这样,您的代码已经是最佳的或几乎是最佳的。如果没有,找出最大的问题是什么,然后集中精力解决这个问题。

【讨论】:

    猜你喜欢
    • 2013-03-22
    • 2016-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-27
    • 1970-01-01
    • 2019-03-18
    • 1970-01-01
    相关资源
    最近更新 更多