虚函数的性能真的那么差?

科技   2024-04-10 20:05   浙江  

在查阅某个问题的时候,突然看到了关于各个操作的性能损耗,今天就借助这篇文章,聊聊我们印象中性能很差的虚函数~~。

关于虚函数

对于虚函数(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毁所有多线程只是可选,如果单线程能完成且性能不错的前提下,慎用多线程,毕竟其上下文切换开销很大异常处理的性能损耗超过我们想象尽量避免系统/内核调用

最后,以潇洒哥的一句话,结束本文:

抛开业务场景谈性能优化都是耍流氓

以上

推荐阅读  点击标题可跳转

1、C++26 Pack Indexing

2、谈谈嵌入式 C 语言踩内存问题!

3、C++之父反驳白宫,称拜登政府忽视了现代C++编程语言的优势

CPP开发者
我们在 Github 维护着 9000+ star 的C语言/C++开发资源。日常分享 C语言 和 C++ 开发相关技术文章,每篇文章都经过精心筛选,一篇文章讲透一个知识点,让读者读有所获~
 最新文章