在查阅某个问题的时候,突然看到了关于各个操作的性能损耗,今天就借助这篇文章,聊聊我们印象中性能很差的虚函数~~。
关于虚函数
对于虚函数(virtual function)的实现机制,在此就不再赘述了,本节我们聊聊关于虚函数的性能消耗这块。
下面且看一个例子:
class Base
{
public:
void fun() {}
virtual void vfun() {}
};
class Derived : public Base
{
public:
void vfun() {}
};
void vfun(Base *b) {
b->vfun();
}
void fun(Base *b) {
b->fun();
}
emm,相信很多人看过Stanley B. Lippman的<<Inside the C++ Object Model>>一书,其中讲到,在编译器的实现机制中,非静态成员函数需要通过对象实例来调用,因为它们隐式地接收一个指向对象本身的this指针作为第一个参数。换句话说,就是编译器对成员函数的实现中,会将成员函数实现成为与普通函数一样,唯一的区别就是在函数参数末尾会增加一个隐式参数-this指针。
那么,上面Base类中,fun()函数的实现,在编译器看来,可能如下:
M4BaseFvvE(Base * _this)
继续看下面这段代码:
int main() {
Base *b1 = nullptr;
Base *b2 = nullptr;
b1 = new Base;
b2 = new Derived;
b1->vfun();
b2->vfun();
}
在上述代码中,有两个Base类型的指针,分别指向基类Base和派生类Derived,那么其调用机制又如何呢?
首先,与非virtual函数实现机制一样,对于virtual函数,都会增加一个默认的指向实际对象的this指针。
其次,编译器在包含虚函数的类中添加一个隐含的指针vptr指向类的虚函数表,一般情况下,这个vptr指针在对象的最前面
最后,在运行时,通过查找虚函数表,进而找到正确的应该被调用的函数。
也就是说,对于虚函数的调用,最终,编译器可能会变成:
(*b1->vptr[i])(p); // p为实际对象的地址,即Base对象
(*b1->vptr[i])(p); // p为实际对象的地址,即Derived对象
好了,上述这个过程仅仅是本文的后续内容的一个铺垫,往往,我们说虚函数性能差,是因为虚表的查找过程导致性能较普通函数或者普通成员函数查,嗯,相信很多人和我一样,认为这个差,是很差~~
性能
直到我昨天在查阅某个问题的时候,恰好看了一张各种操作的性能分析图,算是颠覆了之前的某些认知。
好了,图来了~~
从上图可以看出,我们所理解的虚函数性能(准确的说是查虚表)的性能,与L3差不多,整数除法操作的一半性能。
基于上图,可以看下代码中哪些地方可以优化,当然了,可以从底部开始(如果非要从顶部开始的话,也没人拦着~),如果非要总结的话,那就如下几点:
•一次IO毁所有•多线程只是可选,如果单线程能完成且性能不错的前提下,慎用多线程,毕竟其上下文切换开销很大•异常处理的性能损耗超过我们想象•尽量避免系统/内核调用
最后,以潇洒哥的一句话,结束本文:
抛开业务场景谈性能优化都是耍流氓
以上