C++性能榨汁机之虚函数的开销

来源  http://irootlee.com/juicer_vtable/

虚函数的实现

虽然C++标准并没有规定编译器实现虚函数的方式,但是大部分编译器均是采用了虚函数表来实现虚函数,即对于每一个包含虚成员函数的类生成一个虚函数表,一个指向虚函数表的指针被放在对象的首地址(不考虑多继承等复杂情况),虚函数表中存储该类所有的虚函数地址。当使用引用或者指针调用虚函数时,首先通过虚函数表指针找到虚函数表,然后通过偏移量找到虚函数地址并调用。关于虚函数表的更多细节,建议阅读《深度探索C++对象模型》这本书。

虚函数表面上的开销

  1. 空间开销

    首先,由于需要为每一个包含虚函数的类生成一个虚函数表,所以程序的二进制文件大小会相应的增大;其次,对于包含虚函数的类的实例来说,每个实例都包含一个虚函数表指针用于指向对应的虚函数表,所以每个实例的空间占用都增加一个指针大小(32位系统4字节,64位系统8字节)。这些空间开销可能会造成缓存的不友好,在一定程度上影响程序性能。

  2. 时间开销

    虚函数的时间开销主要是增加了一次内存寻址,通过虚函数表指针找到虚函数表,虽对程序性能有一些影响,但是影响并不大。

虚函数隐藏在背后的开销

上述虚函数表面上的开销其实是微不足道的,真正影响虚函数性能的是隐藏在背后的,不被人轻易察觉的,只有对计算机体系结构有一定理解才能探寻出藏在背后的“性能杀手”。

首先我们先看调用虚函数时,在汇编层生成了什么代码:

1
...
movq (%rax), %rax
movq (%rax), %rax
movq -24(%rbp), %rdx
movq %rdx, %rdi
call *%rax
...

上述汇编代码最重要的就是第6行,在AT&T格式汇编中,这是一个间接调用,意义是从%rax指明的地址处读取跳转的目标位置。这也是虚函数调用与普通成员函数的区别所在,普通函数调用是一个直接调用。直接调用与间接调用的区别就是跳转地址是否确定,直接调用的跳转地址是编译器确定的,而间接调用是运行到该指令时从寄存器中取出地址然后跳转。

有了上面的基本认识,我们就可以分析虚函数的性能开销所在了,其实说到底,这个隐藏在背后的关键点就是分支预测器,如果看过我之前的博客,相信对分支预测器已经很熟悉了,如果感觉分支预测器还是很陌生,推荐阅读我以前的分支预测器的四篇文章:

C++性能榨汁机之分支预测器1

C++性能榨汁机之分支预测器2

C++性能榨汁机之分支预测器3

C++性能榨汁机之分支预测器4

有了分支预测器和CPU指令流水线的基本知识,我们可以发现对于直接调用而言,是不存在分支跳转的,因为跳转地址是编译器确定的,CPU直接去跳转地址取后面的指令即可,不存在分支预测,这样可以保证CPU流水线不被打断。而对于间接寻址,由于跳转地址不确定,所以此处会有多个分支可能,这个时候需要分支预测器进行预测,如果分支预测失败,则会导致流水线冲刷,重新进行取指、译码等操作,对程序性能有很大的影响。

网上有部分文章中说对于虚函数这种间接跳转会直接导致流水线冲刷,这种说法明显是自相矛盾的,如果间接跳转必定会导致流水线冲刷,那把这些指令放进流水线的意义何在呢?其实查阅资料就可以知道,Intel和AMD的CPU中存在两级自适应预测器用于预测间接跳转,此预测器可以预测多分支跳转。

总结

本文探究出影响到虚函数调用性能的背后原因是流水线和分支预测,由于虚函数调用需要间接跳转,所以会导致虚函数调用比普通函数调用多了分支预测的过程,产生性能差距的原因主要是分支预测失败导致的流水线冲刷性能开销。

本文的目的并不是为了说明虚函数调用有额外开销而让大家避免使用虚函数,使用不使用虚函数应该由自己程序的需要而定,如果程序逻辑需要使用动态绑定,如果不使用虚函数而是自己实现相应逻辑的话产生的性能损耗一般会比使用虚函数的性能损耗大得多。但对于一些性能敏感的程序,在虚函数可用可不用的时候,可以考虑不使用虚函数以提高性能。

================== End

C++性能榨汁机之虚函数的开销的更多相关文章

  1. RTTI、虚函数和虚基类的实现方式、开销分析及使用指导(虚函数的开销很小,就2次操作而已)

    白杨 http://baiy.cn “在正确的场合使用恰当的特性” 对称职的C++程序员来说是一个基本标准.想要做到这点,首先要了解语言中每个特性的实现方式及其开销.本文主要讨论相对于传统 C 而言, ...

  2. C++虚函数的缺陷

    MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数内存开销太大.在Qt中也没有采用C++中的虚函数机制,原因与此相同,其实这里还有更深层次上的原因,大体说来,多态的底层实现机制只有 ...

  3. 你好,C++(37)上车的人请买票!6.3.3 用虚函数实现多态

    6.3.3  用虚函数实现多态 在理解了面向对象的继承机制之后,我们知道了在大多数情况下派生类是基类的“一种”,就像“学生”是“人”类中的一种一样.既然“学生”是“人”的一种,那么在使用“人”这个概念 ...

  4. C++的静态分发(CRTP)和动态分发(虚函数多态)的比较

    虚函数是C++实现多态的工具,在运行时根据虚表决定调用合适的函数.这被称作动态分发.虚函数很好的实现了多态的要求,但是在运行时引入了一些开销,包括: 对每一个虚函数的调用都需要额外的指针寻址 虚函数通 ...

  5. 虚函数指针sizeof不为sizeof(void*)

    ref:http://bbs.csdn.net/topics/360249561 一个继承了两个虚基类又增加了自己的一个虚函数pif的类,sizeof(指向pif的指针)竟然是8(X86).我是从这里 ...

  6. C++ 中的虚函数表及虚函数执行原理

    为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚 ...

  7. Lua 支持虚函数的解决方案

    概述 lua本身没有提供类似C++虚函数机制,调用的父类方法调用虚函数可能会出现问题. 问题分析 分析这段代码和输出 local Gun = {} -- 示例,实际应用还要考虑构造,虚表等情况 fun ...

  8. 【转】 C++的精髓——虚函数

    虚函数为了重载和多态的需要,在基类中是由定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入原 ...

  9. C++ virtual虚函数

    #include<iostream> using namespace std; class Base{ public: void m() { cout << "it' ...

随机推荐

  1. 从 10.x 到 ArcGIS Pro 的 Python 迁移

    与 ArcGIS Pro 结合使用 Python 的方式与包括 ArcGIS Desktop.ArcGIS Server 以及 ArcGIS Engine 在内的其他 ArcGIS 产品不同. 地理处 ...

  2. 推送kafka消息失败

    晚上变更 怎么都推不过去,蛋疼,睡饱后加了个hosts没想到好了,然后搜了一下,大概是如下的原因 转自 https://www.cnblogs.com/linlianhuan/p/9258061.ht ...

  3. DBeaver 客户端中时间显示问题解决

    最近工作使用了一段时间的macOS,换了新的数据库客户端 DBeaver,无意中发现客户端显示时间不正确.时间保存之后显示比实际时间多13个小时整.可以判断是时区没有配置正确.无意中发现是DBeave ...

  4. ubuntu tensorflow cpu faster-rcnn train data

    (flappbird) luo@luo-All-Series:~/MyFile/tf-faster-rcnn_box$ (flappbird) luo@luo-All-Series:~/MyFile/ ...

  5. 20190615 NACE关于采购订单的输出类型

    项目已经做好的配置,我们复盘一下 一.使用NACE 进入输出控制条件: EF 采购订单,首先看->输出类型 标准是使用 nast 作为记录表 1输出类型, 2过程, 3 存取顺序,4 条件记录: ...

  6. linux查看文件具体时间和大小

    查看具体时间 ll --full-time 查看文件大小: ll -ht 或者du -sh *

  7. 容器时代的持续交付工具---Drone:Drone使用

    上一篇文章里已经介绍了如何安装Drone,下面我们来看下如何使用.还是基于gogs作为git仓储. 首先打开server对应的地址,进入登录页面,输入在启动server时配置的管理员账号(对应的就是g ...

  8. Apache Jmeter教程(一) - 入门

    一.下载Jmeter 登录官网Jmeter下载,得到压缩包jmeter-5.0.tgz,下载地址:http://jmeter.apache.org/download_jmeter.cgi. 二.安装 ...

  9. 计算1+2+...+n

    牛客上面一道题,闲来无事做做陶冶情操. 这一陶冶还真的陶冶出了骚操作 看一下题目吧: 求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及 ...

  10. Burp_Suite_Pro_v1.7.*激活

    先下载工具: 链接:https://pan.baidu.com/s/1ZLvg8eqkgWI94FtrhkcTxg 提取码:9z1j 工具运行需要Java环境.解压完后双击keygen   填一下Li ...