C++性能优化常用技巧

一. 选择合适的数据结构

1.1 map与unordered_map的选择

如果仅仅只需要使用到快速查找的特性,那么unordered_map更加合适,他的复杂度是O(1)。如果还需要排序以及范围查找的能力,那么就选择map。

1.2 vector与list的选择

通常情况下,顺寻存储一些数据,然后通过下标进行查找,就选择vector。那么什么时候选择list呢?常见的一个用法就是在LRU 缓存的实现中,我们会使用list来存放缓存结点,而不是vector,主要原因就在于list的插入和删除是高效的。

1.3 unordered_map与自定义查找表的选择

如果不需要复杂的哈希函数,仅通过数组下标的形式就能完成哈希,比如key是26个字母或者连续的数字。此时只需要定义一个数组就能实现哈希存储(自定义查找表)。

二. 选择合适的算法

算法对性能的影响是值得考虑的,以排序算法举例:

  • 快速排序:适合数据比较均匀的场景,如果数据已经有序或者接近有序,此时快速排序会退化到O(n*n)的复杂度。
  • 堆排序:适合求topK问题。
  • 归并排序:O(n)的空间复杂度,但是排序性能不会因为数据本身已经有序而退化。
  • 冒泡排序:一般情况下不推荐此排序。性能较差。

三. 避免不必要的拷贝

3.1 使用引用

引用可以直接与被引用对象共享同一份存储,可以认为引用是给对象起一个别名。如下示例代码:

// 形参使用引用
void show(const std::string& str)
{// do something
}std::vector<std::string> strs;
/*
对strs进行填充
*/
// 使用引用接收返回值,前提是函数返回对象的引用
const std::string& str = strs[1];
3.2 使用移动语义

移动语义一般用在初始化对象或者给对象赋值时,可以避免对象的拷贝。如下代码:

std::string stra = "abc";
// 拷贝stra对象
std::string strb = stra;
// 移动strb的资源到strc中
std::string strc = std::move(strb);
3.3 返回值优化(RVO)

通常编译器都有RVO优化的功能,它允许直接在调用者的栈帧上构造对象,从而避免了额外的拷贝和资源析构的开销。如下代码:

class A {
public:A() {}~A() {}A(const A& rhs) {}A(A&& rhs) {}
};int func()
{A a;return a;
}int main()
{A a = func();return 0;
}

上述代码中,返回值优化之后,类A的构造函数和析构函数只被调用一次,不会产生临时变量的构造与析构,以及拷贝构造的过程。

四. 合适的内存管理

4.1 智能指针

在管理动态内存时,智能指针可以帮助我们实现更可靠的内存管理,避免内存泄漏等严重问题。C++11中提供了unique_ptr和shared_ptr两个智能指针,那么该如何选择呢?

  • shared_ptr:共享智能指针。一个对象可能在多个地方被共享。
  • unique_ptr:独占智能指针。一个对象只能被一个智能指针对象所拥有。

通常情况下,如果能明确是独占的场景,那么就选择unique_ptr,虽然shared_ptr也能保证正确性,但是后者性能要比前者差30%。因为uniqe_ptr更接近裸指针,而shared_ptr内部实现相对复杂(引用计数、控制块等)。

4.2 内存池

内存池是一个预先申请和分配好的内存区域。在需要申请内存资源时,可以直接在内存池中获取,在释放内存时,将内存返还给内存池。从而避免了频繁的内存分配和释放的过程。google的tcmalloc就提供了这样的功能。

4.3 对象池

对象池同样也是一种预先申请和分配内存的技术,区别于内存池的是,其针对的是特定的类对象的内存管理。如果一个类型需要频繁的进行对象的创建和释放,并且对象的创建比较耗时。那么我们可以选择使用池化技术,预先创建好一定数量的对象,放到对象池中。其实很多池化技术都属于这个范畴,只是针对不同场景,有其独特的叫法,如MySQL的连接池、线程池等等。

4.4 栈内存的管理

通常我们只需要管理堆内存,而栈内存交给操作系统来管理。但是栈内存的申请和初始化依旧是由程序员来控制的,而操作系统只负责内存的分配和释放。考虑如下场景:

void func()
{for (int i = 0; i < 999999; ++i) {char data[1000000];// do something}
}

上述data在每一次循环都需要分配一次内存,然后对其进行操作。如果data的创建可以放到循环体之外进行也不影响程序的正确性。那么data的内存分配只需要一次即可。
事实上,我们还可以继续优化。因为这块内存很大,函数也可能会被频繁调用,每次调用都会重复申请一大块内存。所以考虑创建一个静态的data变量,或者静态的全局变量。只要它能满足程序的正确性要求。何乐而不为呢?当然静态变量在多线程环境下会存在数据竞争的问题。此时还可以考虑使用threadlocal变量

五、减少函数调用

5.1 使用内联函数

一次函数调用涉及两次指令跳转。如果一个函数频繁调用,可以使用内联机制来避免函数的调用。C++11提供了inline关键字,来告诉编译器被修饰的函数可以在调用处进行替换。一般这样的函数是一些功能简单,代码行较少的函数。当然是否内联取决于编译器,inline只是给编译器建议。是否内联可通过查看汇编代码来确认。

5.2 减少函数递归调用

函数递归通常写起来比较方便,并且也更好理解。但是函数递归带来的开销是巨大的,随着递归深度的加深,性能也会受到影响。稍有不慎,甚至会造成栈溢出。所以应该尽量避免使用递归函数,考虑使用迭代或者只使用尾递归(编译器优化,只需要当前的栈帧空间,无需开辟新空间)的方式。

六、多线程处理

多线程可以利用多核特性,同时处理多个任务,从而提高程序性能。多线程常常涉及数据竞争的问题,为了提高多线程的性能,应减少数据竞争,常见的方法有:

  • 使用原子变量。
  • 使用读写锁。
  • 降低锁的持有时间。
  • 使用线程池模型,重复利用线程资源,利用多队列减少工作线程间的数据竞争。
  • 使用无锁数据结构,避免频繁的线程上下文切换。
  • 减少需要共享的状态。

七、编译器优化

在考虑性能问题之前,首先得开启编译器优化,很多时候,代码虽然写的不是最优的,但是在开启编译优化之后,往往能达到很好的优化效果。常见的优化选项:

  • O0:不做任何优化。
  • O1:主要对代码的分支,常量以及表达式等进行优化。
  • O2:会尝试更多的寄存器级的优化以及指令级的优化。
  • O3:在O2的基础上进行更多的函数内联优化,因此目标代码会更大,但执行速度会更快。

八、指令级优化

8.1 SIMD指令

SIMD(单指令多数据)指的是具有多个处理元件的计算机同时对多个数据执行相同操作的过程。以加法为例,通常情况下,我们需要先取操作数1,再取操作数2,然后求和。而SIMD指令则支持同时取多个操作数,然后执行求和运算。也就是说取操作数的过程是并行的。在gcc中可以通过添加ftree-vectorize编译选项,或者开启O2优化,就可以让编译器使用SIMD优化代码。

九、提高缓存命中率

CPU有3级缓存L1、L2、L3,其中L1离核心最近,因此速度也最快。由于缓存有大小限制,因此只有少量的数据和指令会被加载到缓存中,所以经常会出现缓存无法命中的问题。因此提高cache命中率就可以提高程序性能。

9.1 选择合适的数据结构

数据结构多使用连续的存储,如vector,而少使用list、map这种非连续存储类型。因为连续的内存通常会被一起加载到缓存中。

9.2 内存对齐

内存不对齐的情况下,cpu可能需要夸多个内存行去获取数据。从而增加了cpu的访存次数,也增加了缓存不命中的可能性。

9.3 减少条件分支

程序中的条件判断,如 if-else 语句,可以通过逻辑重组或使用分支预测优化来提高缓存命中率。在某些情况下,消除不必要的条件判断或将其重构为更高效的形式可以减少缓存行的加载和卸载,从而提高性能。例如,使用gcc的提供的关键字__builtin_expect来告诉编译器哪个分支命中的概率最高,从而实现优化。

9.4 其他
  • 避免频繁的内存分配与释放,减少内存碎片。
  • 循环展开,减少循环次数。
  • 循环中按行处理数据要比按列处理更高效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/72240.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Towards Graph Foundation Models: A Survey and Beyond

Towards Graph Foundation Models: A Survey and Beyond WWW24 ​#paper/⭐⭐⭐#​ #paper/&#x1f4a1;#​ 背景和动机 背景与意义 随着基础模型&#xff08;如大语言模型&#xff09;在NLP等领域的突破&#xff0c;图机器学习正经历从浅层方法向深度学习的范式转变。GFM…

基于 Python 深度学习的电影评论情感分析可视化系统(2.0 全新升级)

基于 Python 深度学习的电影评论情感分析可视化系统&#xff0c;基于 Flask 深度学习&#xff0c;构建了一个 影评情感分析系统&#xff0c;能够 自动分析影评、计算情感趋势 并 可视化展示&#xff0c;对于电影行业具有重要参考价值&#xff01; 基于 Python 深度学习的电影评…

Cargo, the Rust package manager, is not installed or is not on PATH.

今天在Windows操作系统上通过pip 安装jupyter的时候遇到这个报错&#xff0c;Cargo, the Rust package manager, is not installed or is not on PATH.。 解决办法 官网&#xff1a;https://rustup.rs/# 下载&#xff1a;https://win.rustup.rs/x86_64 安装完成之后&#xff0c…

CSS—text文本、font字体、列表list、表格table、表单input、下拉菜单select

目录 1.文本 2.字体 3.列表list a.无序列表 b.有序列表 c.定义列表 4.表格table a.内容 b.合并单元格 3.表单input a.input标签 b.单选框 c.上传文件 4.下拉菜单 1.文本 属性描述color设置文本颜色。direction指定文本的方向 / 书写方向。letter-spacing设置字符…

开启AI短剧新纪元!SkyReels-V1/A1双剑合璧!昆仑万维开源首个面向AI短剧的视频生成模型

论文链接&#xff1a;https://arxiv.org/abs/2502.10841 项目链接&#xff1a;https://skyworkai.github.io/skyreels-a1.github.io/ Demo链接&#xff1a;https://www.skyreels.ai/ 开源地址&#xff1a;https://github.com/SkyworkAI/SkyReels-A1 https://github.com/Skywork…

数学建模:MATLAB极限学习机解决回归问题

一、简述 极限学习机是一种用于训练单隐层前馈神经网络的算法&#xff0c;由输入层、隐藏层、输出层组成。 基本原理&#xff1a; 输入层接受传入的样本数据。 在训练过程中随机生成从输入层到隐藏层的所有连接权重以及每个隐藏层神经元的偏置值&#xff0c;这些参数在整个…

Android15音频进阶之定位混音线程丢帧问题(一百零八)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…

_ 为什么在python中可以当变量名

在 Python 中&#xff0c;_&#xff08;下划线&#xff09;是一个有效的变量名&#xff0c;这主要源于 Python 的命名规则和一些特殊的使用场景。以下是为什么 _ 可以作为变量名的原因和常见用途&#xff1a; --- ### 1. **Python 的命名规则** Python 允许使用字母&#xff…

Electron+Vite+React+TypeScript开发问题手册

ElectronViteReactTypeScript跨平台开发全问题手册 一、开发环境配置类问题 1.1 依赖安装卡顿&#xff08;国内网络环境&#xff09; 问题现象&#xff1a;执行npm install时卡在node-gyp编译或Electron二进制包下载阶段 解决方案&#xff1a; # 配置国内镜像源 npm config …

【计算机网络入门】初学计算机网络(七)

目录 1. 滑动窗口机制 2. 停止等待协议&#xff08;S-W&#xff09; 2.1 滑动窗口机制 2.2 确认机制 2.3 重传机制 2.4 为什么要给帧编号 3. 后退N帧协议&#xff08;GBN&#xff09; 3.1 滑动窗口机制 3.2 确认机制 3.3 重传机制 4. 选择重传协议&#xff08;SR&a…

《Python实战进阶》No 8:部署 Flask/Django 应用到云平台(以Aliyun为例)

第8集&#xff1a;部署 Flask/Django 应用到云平台&#xff08;以Aliyun为例&#xff09; 2025年3月1日更新 增加了 Ubuntu服务器安装Python详细教程链接。 引言 在现代 Web 开发中&#xff0c;开发一个功能强大的应用只是第一步。为了让用户能够访问你的应用&#xff0c;你需…

GitLab Pages 托管静态网站

文章目录 新建项目配置博客添加 .gitlab-ci.yml其他配置 曾经用 Github Pages 来托管博客内容&#xff0c;但是有一些不足&#xff1a; 在不科学上网的情况下&#xff0c;是没法访问的&#xff0c;或者访问速度非常慢代码仓库必须是公开的&#xff0c;如果设置为私有&#xff0…

TVbox蜂蜜影视:智能电视观影新选择,简洁界面与强大功能兼具

蜂蜜影视是一款基于猫影视开源项目 CatVodTVJarLoader 开发的智能电视软件&#xff0c;专为追求简洁与高效观影体验的用户设计。该软件从零开始编写&#xff0c;界面清爽&#xff0c;操作流畅&#xff0c;特别适合在智能电视上使用。其最大的亮点在于能够自动跳过失效的播放地址…

形象生动讲解Linux 虚拟化 I/O

用现实生活的比喻和简单例子来解释 Linux 虚拟化 I/O&#xff0c;就像给朋友讲故事一样。 虚拟化 I/O 要解决什么问题&#xff1f; 想象你有一栋大房子&#xff08;物理服务器&#xff09;&#xff0c;想把它分割成多个小公寓&#xff08;虚拟机&#xff09;出租。每个租客&…

Java内存管理与性能优化实践

Java内存管理与性能优化实践 Java作为一种广泛使用的编程语言&#xff0c;其内存管理和性能优化是开发者在日常工作中需要深入了解的重要内容。Java的内存管理机制借助于垃圾回收&#xff08;GC&#xff09;来自动处理内存的分配和释放&#xff0c;但要实现高效的内存管理和优…

代码随想录算法训练营第三十天 | 卡码网46.携带研究材料(二维解法)、卡码网46.携带研究材料(滚动数组)、LeetCode416.分割等和子集

代码随想录算法训练营第三十天 | 卡码网46.携带研究材料&#xff08;二维解法&#xff09;、卡码网46.携带研究材料&#xff08;滚动数组&#xff09;、LeetCode416.分割等和子集 01-1 卡码网46.携带研究材料&#xff08;二维&#xff09; 相关资源 题目链接&#xff1a;46. 携…

nvidia驱动更新,centos下安装openwebui+ollama(非docker)

查看centos内核版本 uname -a cat /etc/redhat-release下载对应的程序&#xff08;这个是linux64位版本通用的&#xff09; https://cn.download.nvidia.cn/tesla/550.144.03/NVIDIA-Linux-x86_64-550.144.03.run cudnn想办法自己下一下&#xff0c;我这里是12.x和11.x通用的…

【AIGC系列】4:Stable Diffusion应用实践和代码分析

AIGC系列博文&#xff1a; 【AIGC系列】1&#xff1a;自编码器&#xff08;AutoEncoder, AE&#xff09; 【AIGC系列】2&#xff1a;DALLE 2模型介绍&#xff08;内含扩散模型介绍&#xff09; 【AIGC系列】3&#xff1a;Stable Diffusion模型原理介绍 【AIGC系列】4&#xff1…

51单片机-串口通信编程

串行口工作之前&#xff0c;应对其进行初始化&#xff0c;主要是设置产生波特率的定时器1、串行口控制盒中断控制。具体步骤如下&#xff1a; 确定T1的工作方式&#xff08;编程TMOD寄存器&#xff09;计算T1的初值&#xff0c;装载TH1\TL1启动T1&#xff08;编程TCON中的TR1位…

Windows 10 远程桌面连接使用指南

目录 一、引言 二、准备工作 1、确认系统版本 2、服务器端设置 三、客户端连接 1、打开远程桌面连接程序 2、输入连接信息 3、输入登录凭证 4、开始使用远程桌面 四、移动端连接&#xff08;以 iOS 为例&#xff09; 1、下载安装应用 2、添加远程计算机 3、进行连接…