在我的课程设计中,我广泛使用抽象类和虚函数。我认为虚函数会影响性能,但是我觉得这种性能差异不太明显,而且似乎我正在进行过早的优化。对吗?
在我的课程设计中,我广泛使用抽象类和虚函数。我认为虚函数会影响性能,但是我觉得这种性能差异不太明显,而且似乎我正在进行过早的优化。对吗?
你的问题让我很好奇,所以我测试了我们使用的3GHz顺序PowerPC CPU的一些时间。我运行的测试是创建一个简单的带有get/set函数的四维向量类。
class TestVec
{
float x,y,z,w;
public:
float GetX() { return x; }
float SetX(float to) { return x=to; } // and so on for the other three
}
然后我设置了三个包含1024个这些向量的数组(足够小以适应L1缓存),并运行了一个循环,将它们相互添加(A.x = B.x + C.x)了1000次。我使用定义为inline
、virtual
和常规函数调用的函数进行了测试。以下是结果:
因此,在这种情况下(所有数据都适合缓存),虚函数调用比内联调用慢大约20倍。但是这到底意味着什么呢?每次通过循环时,恰好会调用 3 * 4 * 1024 = 12,288
次函数(1024个向量乘以四个分量乘以每个加法的三个调用),因此这些时间代表了 1000 * 12,288 = 12,288,000
次函数调用。虚循环比直接循环多花费了92毫秒,因此每次调用的额外开销是7纳秒。
从这个结果可以得出结论:是的,虚函数比直接函数慢得多,但是不,除非你计划每秒调用它们一千万次,否则并不重要。
另见:生成的汇编代码对比。
一个好的经验法则是:
除非你能证明它,否则它不是性能问题。
虚函数的使用可能会对性能产生微小影响,但不太可能影响应用程序的整体性能。 寻找性能改进的更好方法在于算法和I/O。
一篇关于虚函数(以及其他内容)的优秀文章是Member Function Pointers and the Fastest Possible C++ Delegates。
当Objective-C(在该语言中,所有方法都是虚拟的)是iPhone的主要语言,而可怕的Java是Android的主要语言时,我认为在我们的3 GHz双核塔上使用C++虚函数是相当安全的。
摘自Agner Fog的《C++软件优化》手册第44页:
调用虚成员函数所需的时间比调用非虚成员函数多几个时钟周期,前提是函数调用语句始终调用相同版本的虚函数。如果版本更改,则会产生10到30个时钟周期的错误预测惩罚。对于虚函数调用的预测和错误预测规则与switch语句相同...
switch
来说并不总是正确的。如果 case
值完全是任意的,那么可以肯定。但是如果所有的 case
都是连续的,编译器可能能够将其优化为跳转表(啊,这让我想起了美好的 Z80 时代),这应该是(缺乏更好的术语)常数时间。并不建议尝试用 switch
替换 vfuncs,这是荒谬的。 ;) - underscore_d没错。在计算机运行速度为100MHz时,每次方法调用都需要在虚函数表中查找才能被调用,这是问题的根源。但是今天...在拥有比我的第一台计算机更多内存的1级缓存下运行3GHz CPU?完全没有问题。从主RAM中分配内存的成本比如果所有函数都是虚函数的成本还要高。
这就像古老的编程时代一样,人们说结构化编程很慢,因为所有代码被分成了函数,每个函数都需要堆栈分配和函数调用!
唯一考虑虚函数性能影响的时候,是当它被大量使用并且被实例化到遍布整个代码中的模板代码中时。即使是这种情况,我也不会花太多精力!
附:想想其他“易于使用”的语言-它们所有的方法都在表层下是虚函数,而它们现在也没有变慢。
在具有大型内存占用的类中,单个vtable指针的成本不高,但是COM中的一些ATL类非常小,如果永远不会发生运行时多态情况,则节省vtable的成本是值得的。
另请参阅 这个其他的SO问题。
顺便提一下,这里有一个我找到的帖子,讨论了CPU时间性能方面的问题。
当类方法不是虚函数时,编译器通常会进行内联处理。相反,当您使用指向某个具有虚函数的类的指针时,真实地址只能在运行时确定。
这可以通过测试很好地说明,时间差约为700%(!):
#include <time.h>
class Direct
{
public:
int Perform(int &ia) { return ++ia; }
};
class AbstrBase
{
public:
virtual int Perform(int &ia)=0;
};
class Derived: public AbstrBase
{
public:
virtual int Perform(int &ia) { return ++ia; }
};
int main(int argc, char* argv[])
{
Direct *pdir, dir;
pdir = &dir;
int ia=0;
double start = clock();
while( pdir->Perform(ia) );
double end = clock();
printf( "Direct %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );
Derived drv;
AbstrBase *ab = &drv;
ia=0;
start = clock();
while( ab->Perform(ia) );
end = clock();
printf( "Virtual: %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );
return 0;
}
++ia
相比,虚函数调用代价昂贵。那又怎样? - Bo Persson