跟我学C++中级篇——虚函数的性能
一、虚函数性能
一般来说,面向对象的设计中,继承和多态是其中两个非常重要的特征。从使用的过程来看,一般应用到继承的,使用多态的可能性就非常大。而多态的实现有很多种,
但开发者通常认为的多态(动多态)一般是指通过虚函数来实现的多态。
虚函数的不同于普通函数,它会通过一个虚表来控制函数的二次跳转或者叫做重定向。在普通的认知里,虚函数这个特征一般是无法进行诸如内联等进行优化的。所以一谈到虚函数都会认为其性能堪忧。
但在前面的分析中也知道了,什么东西都有特殊情况。但无论如何说明,在常识里,虚函数就是要比普通函数的性能要低。那么到底虚函数性能为什么会低?是不是所有情况下都低?下面进行一下分析。
二、用例子看问题
先看对比的例子:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <chrono>
class Parent
{
public:virtual double exeReadDataV(double a, int b){return std::sqrt(a) * std::sin(b);}double exeReadData(double a, int b){return std::sqrt(a) * std::sin(b);}
};int main()
{Parent* p = new Parent();double a = 3.14f;int b = 3;double sum = 0.f;auto t1 = std::chrono::steady_clock::now();for (int num = 0; num < 100000000; num++){//使用一个定值,一个动态值sum += p->exeReadDataV(a,num);//全是定值//sum += p->exeReadDataV(a, b);}auto t2 = std::chrono::steady_clock::now();auto escape1 = t2 - t1;std::cerr << "escape1 is:" << escape1.count() << std::endl;auto t3 = std::chrono::steady_clock::now();for (int num = 0; num < 100000000; num++){sum += p->exeReadData(a,num);//sum += p->exeReadData(a, b);}auto t4 = std::chrono::steady_clock::now();auto escape2 = t4 - t3;std::cerr << "escape2 is:" << escape2.count() << std::endl;return 0;
}
执行结果:
escape1 is:1982749665
escape2 is:1850111712
从上面的执行可以看到,两者的执行基本没区别。这可能打破了不少人的感官认知。无论哪种函数,决定性能的有两个重要环节:一个是调用的开销;另外一个是确定性调用。前者比较好理解,后者则不容易弄明白。其实可以这样理解,一个写代码要尽量降低调用的开销,一个是写出的代码编译器能更准确的知晓上下文,然后进行优化。在前者确定的情况下,后者就非常重要了。而虚函数被大多数人认为性能低的主要原因就在于后者。
虚函数需要一个虚表进行跳转,在内存中这种开销与函数的功能开销相对来说可以忽略。但这种跳转本身意味着大量的未知,而未知就意味着编译无法掌控更多的确定性,而对某些很简单的优化可能都无法进行。正如早期的编译器,在for循环中,直接给一个变量和一个表达式,效率差不少就是这个原因。而后的编译器则对此进行了优化,将其直接转成一个常量值。把上面的例子中注释部分打开并注释当前的执行(即两个参数都为定值的情况):
执行结果:
escape1 is:867604532
escape2 is:100431290
在执行的函数调用中,两个参数中一个为定值,一个为变量时,是否调用虚函数或者普通函数,基本运行是差不多的。但是一旦调用的都为定值时,此时普通函数可以直接将两个计算函数std::sqrt(a) 和 std::sin(b)均优化为固定值并只计算一次。此时再看,计算结果可就差了将一个量级了。
而在前面的文章中(“内联补遗”)分析过的虚函数可以内联,恰恰是那种可以明确确定的虚拟函数,可以内联。即编译器知道虚函数不具有多态性的情况下,它可普通函数没有什么区别。把原来的例子拿上来:
class A{
public:inline virtual void Test(){...}
};
class B:public A
{
public:inline virtual void Test(){...}
};
inline void Get(A& a){a.Test();
}
int main(){A a;B b;b.Test();//可以内联//下面不确定Get(b);Get(a);
return 0;
}
那么可以从内联的角度来分析,虚函数为什么会给大家一个性能低的印象?首先为什么内联函数快,主要就是固定地址,编译器优化两大方面。而上面的虚拟函数可以内联,仿佛是与此结论相反,但恰恰提到了能够内联的虚拟函数的情形。互相印证,应该就明白为什么虚函数在多态的情况下性能低的原因了。
总之,虚函数本身不是性能低的代表,但是虚函数多态的调用会影响优化才是性能降低的根源。这些优化包括计算优化、分支跳转优化以及调用优化等等。而这些优化无法被编译器使用,自然也就会使得编译出来的代码有着很多的多余的运行指令。
三、设计上对虚函数数的替代
如果对性能的敏感性不强,那么如何使用虚函数不是一个多大的问题。可是如果实际情况对此要求比较严格时,可以考虑用如下的方式来解决虚函数的使用问题:
1、模板的方法如CRTP(奇异递归)
2、使用宏(不推荐)
3、通过设计模式等设计方法实现(如访问者模式等 )
4、使用一些技术或方法绕开多态,比如就直接写多个类然后直接控制
到底如何使用或不使用虚函数,是根据实际情况来决定的。还是那句话,没有一个技术是包打天下的。
四、总结
学习知识不是简单的为了会用,而是能够灵活的运用。要想灵活的运用,则必须掌握技术本质的内涵。只有把其内在体系掌握,才能在具体的场景上发挥其优势。这也是总说的从必然世界到自由世界的一个哲学问题。
相关文章:
跟我学C++中级篇——虚函数的性能
一、虚函数性能 一般来说,面向对象的设计中,继承和多态是其中两个非常重要的特征。从使用的过程来看,一般应用到继承的,使用多态的可能性就非常大。而多态的实现有很多种, 但开发者通常认为的多态(动多态&…...

trl - 微调、对齐大模型的全栈工具
文章目录 一、关于 TRL亮点 二、安装1、Python包2、从源码安装3、存储库 三、命令行界面(CLI)四、如何使用1、SFTTrainer2、RewardTrainer3、PPOTrainer4、DPOTrainer 五、其它开发 & 贡献参考文献最近策略优化 PPO直接偏好优化 DPO 一、关于 TRL T…...

GuLi商城-商品服务-API-品牌管理-品牌分类关联与级联更新
先配置mybatis分页: 品牌管理增加模糊查询: 品牌管理关联分类: 一个品牌可以有多个分类 一个分类也可以有多个品牌 多对多的关系,用中间表 涉及的类: 方法都比较简单,就不贴代码了...

【linux】服务器ubuntu安装cuda11.0、cuDNN教程,简单易懂,包教包会
【linux】服务器ubuntu安装cuda11.0、cuDNN教程,简单易懂,包教包会 【创作不易,求点赞关注收藏】 文章目录 【linux】服务器ubuntu安装cuda11.0、cuDNN教程,简单易懂,包教包会一、版本情况介绍二、安装cuda1、到官网…...

在 Apifox 中如何高效批量添加接口请求 Body 参数?
在使用 Apifox 进行 API 设计时,你可能会遇到需要添加大量请求参数的情况。想象一下,如果一个接口需要几十甚至上百个参数,若要在接口的「修改文档」里一个个手动添加这些参数,那未免也太麻烦了,耗时且易出错。这时候&…...

专业PDF编辑工具:Acrobat Pro DC 2024.002.20933绿色版,提升你的工作效率!
软件介绍 Adobe Acrobat Pro DC 2024绿色便携版是一款功能强大的PDF编辑和转换软件,由Adobe公司推出。它是Acrobat XI系列的后续产品,提供了全新的用户界面和增强功能。用户可以借助这款软件将纸质文件转换为可编辑的电子文件,便于传输、签署…...

车载音视频App框架设计
简介 统一播放器提供媒体播放一致性的交互和视觉体验,减少各个媒体应用和场景独自开发的重复工作量,实现媒体播放链路的一致性,减少碎片化的Bug。本文面向应用开发者介绍如何快速接入媒体播放器。 主要功能: 新设计的统一播放U…...

StarRocks on AWS Graviton3,实现 50% 以上性价比提升
在数据时代,企业拥有前所未有的大量数据资产,但如何从海量数据中发掘价值成为挑战。数据分析凭借强大的分析能力,可从不同维度挖掘数据中蕴含的见解和规律,为企业战略决策提供依据。数据分析在营销、风险管控、产品优化等领域发挥…...
VUE中setup()
在Vue中,setup() 函数是Vue 3.0及更高版本引入的一个重要特性,它是Composition API的入口点。setup() 函数用于初始化组件的状态和逻辑,包括定义响应式数据、方法和生命周期钩子。以下是关于setup() 函数的详细解释: 1. 作用与特…...

【单元测试】SpringBoot
【单元测试】SpringBoot 1. 为什么单元测试很重要?‼️ 从前,有一个名叫小明的程序员,他非常聪明,但有一个致命的缺点:懒惰。小明的代码写得又快又好,但他总觉得单元测试是一件麻烦事,觉得代码…...

分布式搜索引擎ES-elasticsearch入门
1.分布式搜索引擎:luceneVS Solr VS Elasticsearch 什么是分布式搜索引擎 搜索引擎:数据源:数据库或者爬虫资源 分布式存储与搜索:多个节点组成的服务,提高扩展性(扩展成集群) 使用搜索引擎为搜索提供服务。可以从海量…...

TCP三次握手与四次挥手详解
1.什么是TCP TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的通信协议,属于互联网协议族(TCP/IP)的一部分。TCP 提供可靠的、顺序的、无差错的数据传输服务&…...
【Windows】操作系统之任务管理器(第一篇)
一、操作系统简介 Windows操作系统是由微软公司(Microsoft)开发的一款图形操作系统,它以其强大的功能和广泛的用户基础,成为了目前世界上用户使用最多、兼容性最强的操作系统之一。以下是关于Windows操作系统的详细介绍ÿ…...

图同构的必要条件
来源:离散数学...
Django获取request请求中的参数
支持 post put json_str request.body # 属性获取最原始的请求体数据 json_dict json.loads(json_str)# 将原始数据转成字典格式 json_dict.get("key", "默认值") # 获取数据参考 https://blog.csdn.net/user_san/article/details/109654028...

kotlin compose 实现应用内多语言切换(不重新打开App)
1. 示例图 2.具体实现 如何实现上述示例,且不需要重新打开App ①自定义 MainApplication 实现 Application ,定义两个变量: class MainApplication : Application() { object GlobalDpData { var language: String = "" var defaultLanguage: Strin…...

记录些MySQL题集(16)
MySQL 存储过程与触发器 一、初识MySQL的存储过程 Stored Procedure存储过程是数据库系统中一个十分重要的功能,使用存储过程可以大幅度缩短大SQL的响应时间,同时也可以提高数据库编程的灵活性。 存储过程是一组为了完成特定功能的SQL语句集合&#x…...
【算法基础】Dijkstra 算法
定义: g [ i ] [ j ] g[i][j] g[i][j] 表示 v i v_i vi 到 $v_j $的边权重,如果没有连接,则 g [ i ] [ j ] ∞ g[i][j] \infty g[i][j]∞ d i s [ i ] dis[i] dis[i] 表示 v k v_k vk 到节点 v i v_i vi 的最短长度, …...

使用 Flask 3 搭建问答平台(三):注册页面模板渲染
前言 前端文件下载 链接https://pan.baidu.com/s/1Ju5hhhhy5pcUMM7VS3S5YA?pwd6666%C2%A0 知识点 1. 在路由中渲染前端页面 2. 使用 JinJa 2 模板实现前端代码复用 一、auth.py from flask import render_templatebp.route(/register, methods[GET]) def register():re…...

pycharm如何debug for循环里面的错误值
一般debug时,在for循环里面的话,需要自己一步一步点。如果循环几百次那种就比较麻烦。此时可以采用try except的方式来解决 例子如下 #ptyhon debug for循环的代码 num[1,2,3,s,4] ans0 for i in num:try:ansiexcept:print(错误) print(ans) 结果如下&a…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...