C++性能榨汁机之虚函数的开销
C++性能榨汁机之虚函数的开销
来源 http://irootlee.com/juicer_vtable/
虚函数的实现
虽然C++标准并没有规定编译器实现虚函数的方式,但是大部分编译器均是采用了虚函数表来实现虚函数,即对于每一个包含虚成员函数的类生成一个虚函数表,一个指向虚函数表的指针被放在对象的首地址(不考虑多继承等复杂情况),虚函数表中存储该类所有的虚函数地址。当使用引用或者指针调用虚函数时,首先通过虚函数表指针找到虚函数表,然后通过偏移量找到虚函数地址并调用。关于虚函数表的更多细节,建议阅读《深度探索C++对象模型》这本书。
虚函数表面上的开销
空间开销
首先,由于需要为每一个包含虚函数的类生成一个虚函数表,所以程序的二进制文件大小会相应的增大;其次,对于包含虚函数的类的实例来说,每个实例都包含一个虚函数表指针用于指向对应的虚函数表,所以每个实例的空间占用都增加一个指针大小(32位系统4字节,64位系统8字节)。这些空间开销可能会造成缓存的不友好,在一定程度上影响程序性能。
时间开销
虚函数的时间开销主要是增加了一次内存寻址,通过虚函数表指针找到虚函数表,虽对程序性能有一些影响,但是影响并不大。
虚函数隐藏在背后的开销
上述虚函数表面上的开销其实是微不足道的,真正影响虚函数性能的是隐藏在背后的,不被人轻易察觉的,只有对计算机体系结构有一定理解才能探寻出藏在背后的“性能杀手”。
首先我们先看调用虚函数时,在汇编层生成了什么代码:
1 |
... |
上述汇编代码最重要的就是第6行,在AT&T格式汇编中,这是一个间接调用,意义是从%rax指明的地址处读取跳转的目标位置。这也是虚函数调用与普通成员函数的区别所在,普通函数调用是一个直接调用。直接调用与间接调用的区别就是跳转地址是否确定,直接调用的跳转地址是编译器确定的,而间接调用是运行到该指令时从寄存器中取出地址然后跳转。
有了上面的基本认识,我们就可以分析虚函数的性能开销所在了,其实说到底,这个隐藏在背后的关键点就是分支预测器,如果看过我之前的博客,相信对分支预测器已经很熟悉了,如果感觉分支预测器还是很陌生,推荐阅读我以前的分支预测器的四篇文章:
有了分支预测器和CPU指令流水线的基本知识,我们可以发现对于直接调用而言,是不存在分支跳转的,因为跳转地址是编译器确定的,CPU直接去跳转地址取后面的指令即可,不存在分支预测,这样可以保证CPU流水线不被打断。而对于间接寻址,由于跳转地址不确定,所以此处会有多个分支可能,这个时候需要分支预测器进行预测,如果分支预测失败,则会导致流水线冲刷,重新进行取指、译码等操作,对程序性能有很大的影响。
网上有部分文章中说对于虚函数这种间接跳转会直接导致流水线冲刷,这种说法明显是自相矛盾的,如果间接跳转必定会导致流水线冲刷,那把这些指令放进流水线的意义何在呢?其实查阅资料就可以知道,Intel和AMD的CPU中存在两级自适应预测器用于预测间接跳转,此预测器可以预测多分支跳转。
总结
本文探究出影响到虚函数调用性能的背后原因是流水线和分支预测,由于虚函数调用需要间接跳转,所以会导致虚函数调用比普通函数调用多了分支预测的过程,产生性能差距的原因主要是分支预测失败导致的流水线冲刷性能开销。
本文的目的并不是为了说明虚函数调用有额外开销而让大家避免使用虚函数,使用不使用虚函数应该由自己程序的需要而定,如果程序逻辑需要使用动态绑定,如果不使用虚函数而是自己实现相应逻辑的话产生的性能损耗一般会比使用虚函数的性能损耗大得多。但对于一些性能敏感的程序,在虚函数可用可不用的时候,可以考虑不使用虚函数以提高性能。
================== End
C++性能榨汁机之虚函数的开销的更多相关文章
- RTTI、虚函数和虚基类的实现方式、开销分析及使用指导(虚函数的开销很小,就2次操作而已)
白杨 http://baiy.cn “在正确的场合使用恰当的特性” 对称职的C++程序员来说是一个基本标准.想要做到这点,首先要了解语言中每个特性的实现方式及其开销.本文主要讨论相对于传统 C 而言, ...
- C++虚函数的缺陷
MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数内存开销太大.在Qt中也没有采用C++中的虚函数机制,原因与此相同,其实这里还有更深层次上的原因,大体说来,多态的底层实现机制只有 ...
- 你好,C++(37)上车的人请买票!6.3.3 用虚函数实现多态
6.3.3 用虚函数实现多态 在理解了面向对象的继承机制之后,我们知道了在大多数情况下派生类是基类的“一种”,就像“学生”是“人”类中的一种一样.既然“学生”是“人”的一种,那么在使用“人”这个概念 ...
- C++的静态分发(CRTP)和动态分发(虚函数多态)的比较
虚函数是C++实现多态的工具,在运行时根据虚表决定调用合适的函数.这被称作动态分发.虚函数很好的实现了多态的要求,但是在运行时引入了一些开销,包括: 对每一个虚函数的调用都需要额外的指针寻址 虚函数通 ...
- 虚函数指针sizeof不为sizeof(void*)
ref:http://bbs.csdn.net/topics/360249561 一个继承了两个虚基类又增加了自己的一个虚函数pif的类,sizeof(指向pif的指针)竟然是8(X86).我是从这里 ...
- C++ 中的虚函数表及虚函数执行原理
为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚 ...
- Lua 支持虚函数的解决方案
概述 lua本身没有提供类似C++虚函数机制,调用的父类方法调用虚函数可能会出现问题. 问题分析 分析这段代码和输出 local Gun = {} -- 示例,实际应用还要考虑构造,虚表等情况 fun ...
- 【转】 C++的精髓——虚函数
虚函数为了重载和多态的需要,在基类中是由定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入原 ...
- C++ virtual虚函数
#include<iostream> using namespace std; class Base{ public: void m() { cout << "it' ...
随机推荐
- Sublime Text 全局搜索Ctrl+Shift+F快捷键不能用
Sublime Text 全局搜索Ctrl+Shift+F快捷键不能用 和微软输入法的简繁体切换冲突了,关闭输入法的简繁体切换快捷键就好了! 文章来源:刘俊涛的博客 欢迎关注,有问题一起学习欢 ...
- linux安装后需要进行的一些基本设置
修改网络: 在终端中输入:vi /etc/sysconfig/network-scripts/ifcfg-ens33 然后重启网络服务:systemctl restart network.servic ...
- 循环引擎 greenlet 没有显式调度的微线程,换言之 协程
小结: 1. micro-thread with no implicit scheduling; coroutines, in other words. 没有显式调度的微线程,换言之 协程 2. 一个 ...
- x86 linux下如何交叉编译?
答: 需要首先指定两个环境变量CROSS_COMPILE和ARCH 如交叉编译arm64的程序: export CROSS_COMPILE="aarch64-linux-gnu-" ...
- 【MYSQL】存储过程示例
GROUPEMP_EXISTS: ), ), )) LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT ' ...
- cmake log
20:28:54: 为项目RoboticArmProject_CarTerminal_V20190530执行步骤 ...20:28:54: 正在启动 "/usr/bin/make" ...
- linux 测试磁盘iops 方法详解
一.FIO安装 wget http://brick.kernel.dk/snaps/fio-2.0.7.tar.gz yum -y install libaio-devel tar -zxvf ...
- 一个数独引发的惨案:零知识证明(Zero-Knowledge Proof)
导言:原文的作者是著名的Ghost和Spectre 这两个协议的创始团队的领队Aviv Zohar.原文作者说他的这篇原文又是引用了以下这两篇学术论文: How to Explain Zero Kno ...
- Spring Boot Actuator:介绍和使用
Spring Boot Actuator提供一系列HTTP端点来暴露项目信息,用来监控和管理项目.在Maven中,可以添加以下依赖: <!-- Spring boot starter: actu ...
- Flutter Window环境运行(VSCode + 单独运行Android 虚拟机)
官网以及很多网上文章的开发都是基于Android ,因为它能创建不同类型移动设备虚拟机.但个人始终觉得它太庞大,启动慢耗资源,但我们使用Flutter又离不开虚拟机. 经过实践,现在能成功的单独启动移 ...