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

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

相关文章:

C++性能优化常用技巧

一. 选择合适的数据结构 1.1 map与unordered_map的选择 如果仅仅只需要使用到快速查找的特性&#xff0c;那么unordered_map更加合适&#xff0c;他的复杂度是O(1)。如果还需要排序以及范围查找的能力&#xff0c;那么就选择map。 1.2 vector与list的选择 通常情况下&#…...

IntelliJ IDEA集成MarsCode AI

IntelliJ IDEA集成MarsCode AI IDEA中安装插件 安装完毕之后登录自己的账号 点击链接&#xff0c;注册账号 https://www.marscode.cn/events/s/i5DRGqqo/ 可以选择不同的模型...

数据挖掘工程师的技术图谱和学习路径

数据挖掘工程师的技术图谱和学习路径: 1.基础知识 数据挖掘工程师是负责从大量数据中发现潜在模式、趋势和规律的专业人士。以下是数据挖掘工程师需要掌握的基础知识: 数据库知识:熟悉关系数据库和非关系数据库的基本概念和操作,掌握SQL语言。 统计学基础:了解统计学的基…...

Excel基础(详细篇):总结易忽视的知识点,有用的细节操作

目录 基础篇Excel主要功能必会快捷键LotusExcel的文件类型工作表基本操作表项操作选中与缩放边框线 自动添加边框线格式刷设置斜线表头双/多斜线表头不变形的:双/多斜线表头插入多行、多列单元格/行列的移动冻结窗口 方便查看数据打印的常见问题Excel格式数字格式日期格式文本…...

基因枷锁下的太空梦 —— 千钧一发电影观后感

目录 1 人物介绍 2 电影名解读 3 电影开头 3.1 电影开头的两段话 3.2 片头设计 4 电影正文 4.1 “杰罗米”各种诡异的行为 4.2 文森特 – 失败的man 4.3 真正的杰罗米以及假基因身份证 4.4 文森特新征程 4.5 基因人的不容易 4.6 睫毛被查出有问题 4.7 文森特身份初…...

leetcode第40题组合总和Ⅱ

原题出于leetcode第40题https://leetcode.cn/problems/combination-sum-ii/题目如下&#xff1a; 给定一个候选人编号的集合 candidates &#xff08;candidate中有重复的元素&#xff09;和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合…...

迷你世界脚本状态接口:Buff

状态接口&#xff1a;Buff 迷你世界 更新时间: 2023-04-26 20:07:54 具体函数名及描述如下: 序号 函数名 函数描述 1 addBuff(...) 给对象附加效果 2 removeBuff(...) 给对象移除指定效果 3 clearAllBuff(...) 给对象清除所有效果 4 clearAllBadBu…...

Java中Stream流的详细使用介绍

Java中Stream流的详细使用介绍 **1. 创建 Stream**从集合创建从数组创建使用 Stream.of 创建创建无限流 **2. 中间操作**过滤&#xff1a;filter映射&#xff1a;map去重&#xff1a;distinct排序&#xff1a;sorted截取&#xff1a;limit 和 skip **3. 终端操作**收集&#xf…...

【重构小程序】升级JDK1.8、SpringBoot2.x 到JDK17、Springboot 3.x(一)

前言 最近想着把大火的deepseek 迁移到小程序里&#xff0c;基于刷题小程序的数据库做一个RAG应用&#xff0c;来进一步扩展答案解析&#xff0c;帮助用户解答相关问题。但是由于之前做的项目都要老了&#xff0c;并不支持spring 的AI模块&#xff0c;因此&#xff0c;我打算先…...

功能丰富的自动化任务软件zTasker_2.1.0_绿色版_屏蔽强制更新闪退

&#x1f680; zTasker 一键式效率倍增器使用指南 &#x1f64f; 致谢 首先感谢开发者提供如此高效的工具&#xff01; 软件本身功能强大&#xff0c;但部分机制需特别注意&#xff01; &#x1f4d6; 软件概述 zTasker 是一款通过自动化脚本/任务流实现效率飞跃的生产力工…...

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

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

Java 9 到 Java 21 新特性全解析:从语法简化到API增强

一、新特性的概述 纵观Java这几年的版本变化&#xff0c;在Java被收入Oracle之后&#xff0c;Java以小步快跑的迭代方式&#xff0c;在功能更新上迈出了更加轻快的步伐。基于时间发布的版本&#xff0c;可以让Java研发团队及时获得开发人员的反馈&#xff0c;因此可以看到最近…...

LeeCode题库第三十九题

39.组合总和 项目场景&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同…...

卫星网络仿真平台:IPLOOK赋能空天地一体化通信新生态​

卫星仿真平台 在6G技术加速演进与天地一体化网络建设的大背景下&#xff0c;卫星通信作为地面网络的重要补充&#xff0c;正成为全球通信覆盖的关键支撑。IPLOOK凭借其深厚的技术积累与创新实践&#xff0c;推出的卫星网络仿真平台​&#xff08;SCEPS&#xff09;&#xff0c…...

(十一)基于vue3+mapbox-GL实现模拟高德实时导航轨迹播放

要在 Vue 3 项目中结合 Mapbox GL 实现类似高德地图的实时导航轨迹功能,您可以按照以下步骤进行: 安装依赖: 首先,安装 mapbox-gl 和 @turf/turf 这两个必要的库: npm install mapbox-gl @turf/turf引入 Mapbox GL: 在组件中引入 mapbox-gl 并初始化地图实例: <templ…...

计算机面试项目经历描述技巧

在计算机类岗位的面试中&#xff0c;项目经历是面试官评估候选人技术能力、问题解决能力和实战经验的核心环节。以下是专业化的项目经历描述策略&#xff08;附模板框架&#xff09;&#xff1a; 一、结构化表达框架&#xff08;STAR-RT模型&#xff09; Situation&#xff08;…...

132. 分割回文串 II

简单分析 输入的参数是字符串s&#xff0c;返回值是最小的切割次数。那这个问题的典型解法应该是动态规划&#xff0c;因为我们需要找最优解&#xff0c;而每一步的选择可能会影响后面的结果&#xff0c;但可以通过子问题的最优解来构建整体最优解。 那么动态规划的状态如何定…...

【每日学点HarmonyOS Next知识】全局调整字体、h5选择框无法取消选中、margin不生效、Length转换为具体值、Prop和link比较

【每日学点HarmnoyOS Next知识】全局调整字体、h5选择框无法取消选中、margin不生效、Length转换为具体值、Prop和link比较 1、HarmonyOS 是否存在统一调整全局字体大小的方法&#xff1f; 是否存在统一调整全局字体大小的方法 可以用动态属性&#xff0c;自定义class实现At…...

九、Spring Boot:自动配置原理

深入解析 Spring Boot 自动配置原理 Spring Boot 的自动配置机制是其最核心的特性之一&#xff0c;它极大地简化了 Spring 应用的初始搭建和开发过程。通过自动配置&#xff0c;Spring Boot 能够根据项目的依赖和配置自动加载和配置 Spring 应用的各个部分。本文将深入探讨 Sp…...

(动态规划 最长重复子数组)leetcode 718

思路就是建立一个二维的dp数组&#xff0c;只要nums1[i]nums2[j]&#xff08;nums1和nums2出现重复元素就置1 并加上左上角的值) 为什么代码是nums1 i-1和nums2 i-1 答&#xff1a;因为i和j以1为初始值开始遍历的 为什么要这么做并且为什么要加dp【i-1】【j-1】&#xff1f; …...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...