虚函数所造成的性能损失

假设在一个线程同步环境中,有类似下面所示的代码段:

//进入线程同步

nNum++;

//退出线程同步

以win32为例,如我们所知,线程同步工具有临界区,互斥体,信号量。我们可以任意选择一个,为了简单很可能我们就选择了临界区。假如我们需要同步的代码非常简单,我非常建议不需要使用c++的任何功能。但是,很可能没这么幸运,很可能你的代码会被很多人修改,很可能同步的时候需要异常退出,很可能同步的里面还有点逻辑处理,很可能你在退出的时候没有释放锁。那么你就悲剧了。

C++在这里一点上就体现出了与生俱来的优势,构造函数和析构函数。假如我们把获得锁写进构造函数里面,释放锁写进析构函数里面,就能保证任意情况的退出之前都能够释放锁,从而不会造成死锁。

还有个问题是我们应该如何实现这些不同的同步工具。如果,我们分别写三个类,CCriticalSection,CMutex,CSemaphore,这三个类不是继承自同一个基类的,而且这三个类里面也没有任何的虚函数,而且我们把操作都写成内联的。那么,在效率上与直接用C相比基本没有差别。因为,所有额外的调用开销都内联了。在不丧失效率的同时保持了灵活性,这确实是非常强大的地方。

万一你说为了灵活性,需要让这三个类继承同一个基类,CBaseLock,同时需要把析构函数定义成虚的,这样就会造成很多额外的性能损失。比如,可能需要构造和析构基类对象,需要初始化虚函数指针,执行阶段需要通过虚函数指针间接调用,还有一个最重要的损失,编译器无法内联虚函数,如果虚函数比较小,那么相对于原来的情况,这是一个巨大的性能损失。某种意义上,我们可以忽视,基类对象的构造和析构,以及虚函数指针的初始化和调用时刻的间接寻址,因为这些可以其它方面的因素来抵消。

但是,无法内联一个小的函数所造成的额外开销是很大的。想象一下,比如刚才几个类里面的析构函数只是释放锁这一个操作,肯定是可以内联的,但是现在写成虚函数了,就无法内联了,如果这个函数被调用很多次,比如几百万次,和原来消耗的时候相比,肯定是天壤之别了,因为原来没有函数调用开销,现在有,而且因为函数内部操作太少了,函数调用开销所占据的比例非常大,所以你的设计造成了巨大的性能损失。

本文的观点主要来自提高C++性能的编程技术一书,欢迎吐槽。