我知道多态会增加明显的开销。调用虚函数比调用非虚函数更慢。(所有我的经验都是关于GCC的,但我认为/听说这对任何真正的编译器都是正确的。)
很多时候,同一个对象上会一次又一次地调用同一个虚函数;我知道对象类型不会改变,并且大多数情况下编译器也很容易推断出来:
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
我想知道在这些情况下避免多态带来的开销的最佳方法是什么。
我觉得上转型的想法(如第二个片段所示)并不好:BaseType可能被许多类继承,试图将其上转型到所有这些类将会相当冗长。
另一个想法可能是将obj.f存储在函数指针中(没有测试过,不确定它是否会消耗运行时开销),但是这种方法也不完美:与上述方法一样,它需要编写更多代码,并且无法利用某些优化(例如:如果FinalType :: f是内联函数,则不会被内联--但我猜避免这种情况的唯一方法就是将obj上转型为其最终类型...)
那么,有更好的方法吗?
编辑: 好吧,当然这不会产生太大影响。这个问题主要是想知道是否有什么可以做的,因为看起来这种开销是免费的(这种开销似乎很容易消除),我不明白为什么不能这样做。
像C99的restrict关键字一样,为小优化提供一个简单的关键字,告诉编译器多态对象是固定类型的是我所希望的。
无论如何,只是回答评论,确实存在一些开销。看看这个特定的极端代码:
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。我猜在更便宜的平台上速度可能会慢得多。
BaseType
中的f
是虚函数,并且FinalType
是从BaseType
派生而来的,则FinalType
中的f
也是虚函数。 - James McNellisdynamic_cast<>()
在运行时需要进行检查,而多态的成本仅为单个指针解引用操作。我建议每当你说“开销”这个词时,请确保 准确地 表明此开销是什么,至少在第一次谈到这个开销时如此。这样我们就清楚我们要消除的是什么。那么,现在,我猜你对这两种方法进行了剖析,发现多态比你的 hack 更慢了? - wilhelmtellFinalType::f
的参数类型与BaseType::f
的参数类型相同,则FinalType::f
是虚函数并覆盖了BaseType::f
。在派生类中是否使用关键字virtual
声明函数都没有关系。在您的示例中,由于f
没有参数,因此FinalType::f
确实覆盖了BaseType::f
。 - James McNellis