当前位置: 首页 > news >正文

C/C++代码性能优化——编程实践

1. 编程实践

在一些关键的地方,相应的编程技巧能够给性能带来重大提升。

1.1. 参数传递

传递非基本类型时,使用引用或指针,这样可以避免传递过程中发生拷贝。参数根据是否需要返回,相应加上const修饰,代码更安全,且编译器能够更大可能地进行参数优化。

1.2. 函数返回

函数返回非基本类型时,同样会发生拷贝,降低性能。C++代码中使用右值引用和返回值优化,不影响性能。

1.3. 循环展开

循环为什么慢,一次循环就要产生自加、比较、跳转3条指令。减少循环次数,就能提升性能。尤其是针对一些循环体内代码少的情况,性能影响更大。如下示例:

int64_t calc1(int64_t n)
{int64_t fact = 1;for (int64_t i = 1; i < n; i++){fact += i;}return fact;
}int64_t calc2(int64_t n)
{int64_t fact = 1;for (int64_t i = 1; i < n; i += 4){fact += i;fact += i + 1;fact += i + 2;fact += i + 3;}return fact;
}int64_t calc3(int64_t n)
{int64_t fact = 1;for (int64_t i = 1; i < n; i += 8){fact += i;fact += i + 1;fact += i + 2;fact += i + 3;fact += i + 4;fact += i + 5;fact += i + 6;fact += i + 7;}return fact;
}

gcc分别测试优化级别-O2和-O3的效果,结果显示循环展开效果明显,但是-O3优化级别下展开4层和8层几无差异。

C:\Mingw64\mingw64\bin\g++.exe -std=c++17 -Wall -Wextra -g -O2 -mavx2 -Iinclude -c -MMD src/main.cpp  -o src/main.o
C:\Mingw64\mingw64\bin\g++.exe -std=c++17 -Wall -Wextra -g -O2 -mavx2 -Iinclude -o output\main.exe src/main.o  -Llib
Executing 'all' complete!
Calc1  932355974711512065:seconds: 26.159987
Calc2 932356074711512065:seconds: 19.535794
Calc3 932356074711512065:seconds: 9.783930C:\Mingw64\mingw64\bin\g++.exe -std=c++17 -Wall -Wextra -g -O3 -mavx2 -Iinclude -c -MMD src/main.cpp  -o src/main.o
C:\Mingw64\mingw64\bin\g++.exe -std=c++17 -Wall -Wextra -g -O3 -mavx2 -Iinclude -o output\main.exe src/main.o  -Llib
Executing 'all' complete!
Calc1  932355974711512065:seconds: 13.093723
Calc2 932356074711512065:seconds: 6.641366
Calc3 932356074711512065:seconds: 6.605240

1.3. 查表

例如计算char类型中bit1的个数,事先准备256大小的数组,存储对应下标的bit1个数。这样在使用时,直接通过数据下标来查询对应的bit1个数,性能非常好。

1.4. 慎用位域

位域节省空间,但是其读写性能非常差,在性能关键处,慎用位域变量。

1.5. 尾递归

我们知道递归容易导致栈爆了,但是很多场景下递归又非常好用。如何避免递归调用栈爆了呢?使用尾递归技术。

尾递归的递归调用必须是函数体内的最后一个操作。这意味着在递归调用之后不应有任何其他计算或表达式。这个要求是为了确保在递归调用之后没有需要保存的局部变量或表达式结果,从而可以通过直接替换参数值并跳转到函数开头来优化。可以简单理解为递归调用发生时,前面的临时变量都可以覆盖操作,不用保存,这样就可以优化栈内存不断增加的问题。示例:

unsigned long long factorialTail(int64_t n, unsigned long long result)
{if (n == 0){return result;}return factorialTail(n - 1, result * n);
}unsigned long long factorial(int64_t n)
{if (n == 0){return 1;}return n * factorial(n - 1);
}

factorial在很多讲解中被认为不符合尾递归优化,因为要暂存n,可能导致栈爆了。但是现代编译器很聪明,只要开启了-O2或-O3即会开启尾递归优化,上面两个代码都可以正常优化,无论多么深的调用,都不会异常。

1.6. 位运算替换算术运算

位运算在2的倍数操作时,非常方便,性能比较好。如

int x = y << 3;  // 相当于y*8
int x = y >>4;   // 相当于y/16
int x = y & 7;    // 相当于y%8

在低功耗嵌入式32位MCU中,位操作一般需要一个指令周期完成操作。而乘法要2个指令周期。在不支持浮点运算的MCU中,除法是编译器通过乘法操作来模拟的,所以性能更低。取余操作类似除法操作,性能很低。

所以像这些2的倍数的乘法除法取余操作,使用位运算性能会大幅提升。

1.7. 0大小数组

0大小数组不是C/C++的标准语法,是编译器的扩展语法,其也被称为"柔性数组"(Flexible Array)。armcc和gcc均支持此语法。0大小数组不占用结构大小,只是一个占位符。传统的指针可能导致结构体变量出现缓存不友好,影响性能。如果使用此结构,简单方法,且缓存非常友好。在Windows SDK和Linux内核中均有使用此语法形式。

1.8. 减少循环中的判断

分支预测错误非常影响性能,所以在循环中尽量少用判断。在性能关键处的判断,可以加上__builtin_expect来优化。

// Bad
void calc(bool bFlag)
{init();for (int i = 0; i < 10000000; i++){if (bFlag){dosomeA();}else{doSomeB();}}
}// Good
void calc(bool bFlag)
{init();if (bFlag){for (int i = 0; i < 10000000; i++){dosomeA();}}else{for (int i = 0; i < 10000000; i++){dosomeA();}}
}void main()
{calc(true);calc(false);
}

1.9. const、restrict和static

const和static应用尽用,不仅代码更安全可靠,编译器也能更明确代码的意图,可以更进一步地对代码进行优化,如更好的内联,更好的变量替换等,进而提升性能。

restrict是C99中新引入的关键字,指示指针是唯一访问某个内存区域的,从而帮助编译器进行更好的优化。

1.10. 不定义不使用的返回值

函数定义并不知道函数返回值是否被使用,假如返回值从来不会被用到,应该使用void来明确声明函数不返回任何值。

1.11. 异步计算

1.11.1 单核

要提升性能,就不能让CPU停下来,那么在面对一些高时延IO操作时。有一些外设,如UART,一般配置了中断,这样就不轮询来监听UART,专心做正常的事情,UART中断产生了,就来处理UART即可,这样就可以充分利用CPU。

还有一些外设,操作响应慢,如NAND Flash,如果一直轮询来等待NAND Flash响应,非常浪费CPU资源。此时可以使用异步计算,先去做其他事情,估算到NAND Flash差不多结束操作时,再回来轮询NAND Flash状态进行相应的处理。在等待NAND Flash响应的这段时间,虽然可以去做A事情,但是A事件做到一半的时候,先暂存A事情的相差状态,再去响应NAND Flash。响应完NAND Flash之后,再回来接着恢复A事情的相关状态,继续A事情。这种操作方式,非常影响代码编写。在单核单线程CPU中,无法使用多线程,此时就非常需要一个好的异步架构来解决上述问题。方案有两个,一个是使用一些RTOS的多任务模型,一个是使用协程,都可以提升较好的异步计算方案来应对上述场景。

1.11.2 多核

在多核架构MCU中,依然会面临外设阻塞的问题,如果计算资源足够,可以某个核阻塞等待。如果计算资源有限,可以多核并行计算结合多任务模型或协程来实现异步计算。

1.12. 事件驱动框架

一个好的框架,能够提升代码的整体性能。事件驱动架构就是一个追求实时性能的框架。事件驱动的架构由生成事件流的事件生成者和侦听事件的事件使用者组成 。

事件驱动框架是基于发布-订阅设计模式实现的,生产者产生需要处理的相关事件,消费者订阅想要处理的事件(通过回调函数注册),当事件产生时,事件代理将根据注册信息调用相应的消费者处理。多个消费者之间的采用多核或异步计算模型处理。事件代理可以将所有事件整理之后再来通知相应的消费者处理。

1.13. 生成式AI

生成式AI随着ChatGPT的出现,进化更加快了,越来越聪明了。GPT-4,Claude 3还有Gemini Pro都非常厉害。我们会因为思维、信息的局限,走进一些误区。所以在性能关键处的优化,我们都可以请教生成式AI,让它给我们一些意见或建议,指导我们更好地进行性能优化。优化的内容可以是具体代码,也可以是数据结构或算法的选择,也可以是架构的优缺点分析等。

有些关键的地方,汇编代码更有效率,但是汇编代码编写比较麻烦。虽然内联汇编简化了传参和返回,但是编写依然不容易。此时我们就可以借助生成式AI,如下图所示的Prompt,生成的代码测试直接可用,不用修改。

2. 其他

避免性能负优化,也是一种优化性能的方法。另外,理论和实际可能存在一些误解,关键优化一定要真机验证。

2.1. 交换函数

例如交换变量的函数,有人可能以为不用中间变量是不是效率更高,看起来可能是。但是在编译器性能优化下,交换变量的函数直接被优化掉了,编译器直接将两个变量对应的寄存器交换使用即可。gcc的编译结果略有差异,但是swap_ex函数的性能依然较差。

2.2. volatile

volatile作用是禁止编译器优化变量的访问,强制每次从主存上进行存取。

  1. 硬件寄存器对应的变量,需要实时响应,所以需要禁止优化到寄存器上操作。
  2. 中断函数与主流程函数的交互变量,也需要实时响应,所以要禁止优化。
  3. 多核交互的变量,,也需要实时响应,所以要禁止优化。

其他情况下,不要使用volatile,影响性能。

2.3. 不影响性能的代码

2.3.1. 前置自加和后置自加

i++;
++i;
i--;
--i;
for {;;}
while (1)

如果上述代码未优化,性能上确实有差异,但是在开启优化之后,性能是完全一样的。

2.3.2. 栈变量

void test1()
{int sum = 0;for (int i = 0; i < 10; i++){int temp = i*2;sum += temp;}
}void test2()
{int sum = 0;int temp = 0;for (int i = 0; i < 10; i++){temp = i*2;sum += temp;}
}

栈变量和堆变量不一样。堆变量需要申请释放。栈变量不需要申请释放,用与不用栈内存都在那里放着的。C99之后支持的新语法,栈变量可以随便放,不需要放置在块作用域的最前面。栈遵循最小作用域原则即可。

2.3.3. 寄存器

最终实际参与计算的都是寄存器,32位CPU上的大小都是32位的,64位CPU上一般是兼容32寄存器,也即64位CPU有两套寄存器。

在32位CPU上,计算时,无论是int,short还是char类似,最终都是加载到32位寄存器上进行计算,最终结果也是存在32位寄存器上。也就是说,参与计算的变量是int还是short或char,不影响计算的性能。

在64位CPU上,在数据真实大小小于32位时,用int64还是用int32参与计算,性能是一样的。

2.4. 避免过早优化

著名计算机科学家、图灵奖得主,Donald Knuth曾说过:Premature optimization is the root of all evil (过早优化是万恶之源)。

针对x86_64或Cortext-A系列,现在的编译器和CPU非常智能化,能够帮你极好地优化代码执行性能。所以在开发前期,不必过分花力气去优化代码。在后期发现需要提升性能的时候,再来针对性地优化代码,收益付出比会更大。

避免过早优化不是说设计之初始不考虑优化,而是不要花过多时间去关注一些非优先项的性能优化。

2.5. 验证

由于编译器和处理器的发展,有一些优化它们已经做得很好。过分的手动优化,反倒会干扰编译器和处理器来进行优化。如循环展开,预取指令等,针对一些基本的代码结构,编译器能够做得比较好,所以自行进行优化的代码,一定要进行基准测试。

有一些代码的场景,依据数据局部性的优化和依据分支预测的优化是相斥的,此时同样需要基于实际情况来模拟验证,决定最终优化方案。

相关文章:

C/C++代码性能优化——编程实践

1. 编程实践 在一些关键的地方&#xff0c;相应的编程技巧能够给性能带来重大提升。 1.1. 参数传递 传递非基本类型时&#xff0c;使用引用或指针&#xff0c;这样可以避免传递过程中发生拷贝。参数根据是否需要返回&#xff0c;相应加上const修饰&#xff0c;代码更安全&am…...

JVM—内存可见性

什么是可见性 可见性&#xff1a;一个线程对共享变量值的修改,能够及时地被其他线程看到共享变量&#xff1a;如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量 Java内存模型(JMM) Java内存模型(Java Memory Model)描述了Java程序中各种…...

VScode手动安装vsix格式插件,提示安装插件与code版本不兼容问题

问题描述: vscode手动按装插件提示"插件不兼容code版本 原因方案:修改安装包内的package.json文件中的版本号与vscode版本号对应即可 解决步骤 以(adpyke.codesnap-1.3.4.vsix)安装包为例 手动安装vscode弹出 无法安装扩展“adpyke.codesnap-1.3.4”&#xff0c;它与 …...

K8S Storage

概述 一般情况下&#xff0c;K8S中的Pod都不应该将数据持久化到Pod中&#xff0c;因为Pod可能被随时创建和删除&#xff08;扩容或缩容&#xff09;&#xff0c;即便是StatefulSet或Operator的Pod&#xff0c;也都不建议在Pod里存放数据&#xff0c;可以将数据持久化到Host上。…...

Day54-nginx限速-访问日志-错误日志精讲

Day54-nginx限速-访问日志-错误日志精讲 测试请求限制连接限制&#xff08;limit_conn&#xff09;下载速度限制(limit_rate) ngx_http_core_module综合配置1.Nginx状态监控1.1 Nginx status介绍1.2 Nginx status配置1.3 基本状态数据如下所示&#xff1a;&#xff08;注意本地…...

SQL经典面试题

这里写目录标题 1 背概念2 学例子 1 背概念 1 事务 事务是最小的不可在分的工作单元&#xff0c;事务的操作要么同时成功,要么同时失败。 ACID: 原子性、一致性、隔离性、持久性 2 约束 主键约束&#xff1b;外键约束&#xff08;少用&#xff0c;会增加程序的耦合性&#xff…...

Java基础知识总结(14)

map集合 /* java.util.Map接口中常用的方法 1、Map和Collection 没有继承关系 2、Map集合以key和value的方式存储数据&#xff1a;键值对key和valuea都是引用数据类型key和value都是存储对象的内存地址key起到主导地位&#xff0c;value是key的一个附属品 3、Map接口中常用的方…...

MacOS - GCC 版本升级解决方案

Mac 中自带的 GCC 版本是 4.2.1&#xff0c;由于版本太低&#xff0c;在很多操作的时候会报错。因此需要对其进行升级&#xff0c;这里使用 Homebrew 来下载最新的 GCC。 安装 Homebrew MacOS 的终端中输入如下的命令来安装 Homebrew $ /usr/bin/ruby -e "$(curl -fsSL …...

小程序绕过 sign 签名

之前看到了一篇文章 小程序绕过sign签名思路 之前在做小程序渗透时也遇到了这种情况&#xff0c;但是直接放弃测试了&#xff0c;发现这种思路后&#xff0c;又遇到了这种情况&#xff0c;记录下过程 并没有漏洞分享&#xff0c;仅仅是把小程序也分享出来&#xff0c;方便大家…...

【Canvas与艺术】绘制动态太极图

【图例】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>绘制旋转太极图</title><style type"text/css"&g…...

Llama 2 模型

非常清楚&#xff01;&#xff01;&#xff01;Llama 2详解 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/649756898?utm_campaignshareopn&utm_mediumsocial&utm_psn1754103877518098432&utm_sourcewechat_session一些补充理解&#xff1a; 序列化&#xff…...

SQLiteC/C++接口详细介绍sqlite3_stmt类(十一)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;十&#xff09; 下一篇&#xff1a; SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;十二&#xff09; 43、sqlite3_reset sqlite3_reset 函数用于重置已经编…...

【理解机器学习算法】之Clustering算法(Agglomerative Clustering)

聚合聚类(Agglomerative Clustering)是一种层次聚类算法&#xff0c;通过逐步合并或“聚集”它们来构建嵌套聚类。这种方法采用自底向上的方式构建聚类层次&#xff1a;它从将每个数据点作为单个聚类开始&#xff0c;然后迭代合并最接近的聚类对&#xff0c;直到所有数据点合并…...

千帆AppBuilder开发参考-应用API调用说明

介绍 百度智能云千帆AppBuilder平台提供了AppBuilder-SDK&#xff0c;开发者可使用SDK&#xff0c;快捷的开发功能&#xff0c;提升开发效率。 AppBuilder-SDK提供了完整的AI原生应用开发套件&#xff0c;包括丰富的开发组件和应用示例代码。开发组件包括大模型组件、AI能力组…...

python自定义日历库,与对应calendar库函数功能基本一致

目录 自定义日历库 常用列表 日期列表 常用函数 闰年判断 月份天数 元旦序号 日历表头 星期序号 序号及天数 月历字串 打印月历 年历字串 打印年历 对比测试 测试结果 完整代码 运行结果 自定义日历库 自定义日历库函数&#xff0c;并使得其与python calend…...

css3鼠标悬停图片特效,图片悬停效果源码

特效介绍 css3鼠标悬停图片特效,图片悬停效果源码&#xff0c;可以在网页上面作为自己的动态加载名片&#xff0c;放到侧边栏或者网站合适的位置即可 动态效果 代码下载 css3鼠标悬停图片特效,图片悬停效果源码...

使用CSS3画出一个叮当猫HTML源码

我们经常使用PS或者Flash制作动画&#xff0c;本文则介绍了如何用CSS3画出个叮当猫&#xff0c;实现过程很有趣&#xff0c;感兴趣的朋友可以参考一下 首先&#xff0c;先把HTML结构搭建好&#xff1a; <div class"wrapper"> <!--叮当猫整体--> <di…...

Spring Boot 自动化单元测试类的编写过程

前言 Web环境模拟测试 企业开发不仅要保障业务层与数据层的功能安全有效&#xff0c;也要保障表现层的功能正常。但是我们一般对表现层的测试都是通过postman手工测试的&#xff0c;并没有在打包过程中代码体现表现层功能被测试通过。那么能否在测试用例中对表现层进行功能测…...

复试专业前沿问题问答合集8-3——RNN、Hadoop、GPT大语言模型

复试专业前沿问题问答合集8-3——RNN、Hadoop、GPT大语言模型 深度学习中的的RNN、Hadoop、GPT大语言模型的原理关系问答: GPT(Generative Pre-trained Transformer)和RNN(Recurrent Neural Network)是两种在自然语言处理(NLP)领域广泛使用的深度学习模型。它们在处理…...

序列的使用

目录 序列的创建 序列的使 Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 在许多数据库之中都会存在有一种数据类型 — 自动增长列&#xff0c;它能够创建流水号。如果想在 Oracle 中实现这样的自动增长列&#xff0c;可…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG

TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码&#xff1a;HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...

Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程

鸿蒙电脑版操作系统来了&#xff0c;很多小伙伴想体验鸿蒙电脑版操作系统&#xff0c;可惜&#xff0c;鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机&#xff0c;来体验大家心心念念的鸿蒙系统啦&#xff01;注意&#xff1a;虚拟…...