性能优化笔记

性能优化转载

https://www.cnblogs.com/tengzijian/p/17858112.html

性能优化的一般策略及方法

简言之,非必要,不优化。先保证良好的设计,编写易于理解和修改的整洁代码。如果现有的代码很糟糕,先清理重构,然后再考虑优化。

常见性能问题元凶

a. 输入/输出操作

不必要的 I/O 操作是最常见的导致性能问题的罪魁祸首。比如频繁读写磁盘上的文件、通过网络访问数据库等。

b. 缺页
c. 系统调用

系统调用需要进行上下文切换,保存程序状态、恢复内核状态等一些步骤,开销相对较大。对磁盘的读写操作、对键盘、屏幕等外设的操作、内存管理函数的调用等都属于系统调用。

Linux 系统调用可以通过 strace 查看,qnx 也有 tracelogger 等工具

d. 解释型语言

一般来说,C/C++/VB/C# 这种编译型语言的性能好于 Java 的字节码,好于 PHP/Pyhon 等解释型语言。

e. 错误

还有很大很一部分导致性能问题的原因可以归为错误:忘了把调试代码(如保存 trace 到文件)关闭,忘记释放资源/内存泄漏、数据库表设计缺陷(常用表没有索引)等。

常见操作的相对开销

操作示例相对耗时(C++)
整数赋值(基准)i = j1
函数调用
普通函数调用(无参)foo()1
普通函数调用(单参)foo(i)1.5
普通函数调用(双参)foo(i,j)2
类的成员函数调用bar.foo()2
子类的成员函数调用derivedBar.foo()2
多态方法调用abstractBar.foo()2.5
对象解引用
访问对象成员(一级/二级)i = obj1.obj2.num1
整数运算
整数赋值/加/减/乘i = j * k1
整数除法i = j / k5
浮点运算
浮点赋值/加/减/乘x = y * z1
浮点除法x = y / z4
超越函数
浮点根号y = sqrt(x)15
浮点 siny = sin(x)25
浮点对数y = log(x)25
浮点指数y = exp(x)50
数组操作
一维/二维整数/浮点数组下标访问x = a[3][j]1

注:上表仅供参考,不同处理器、不同语言、不同编译器、不同测试环境所得结果可能相差很大!

测量要准确

  • 用专门的 Profiling 工具或者系统时间
  • 只测量你自己的代码部分
  • 必要时需要用 CPU 时钟 tick 数来替代时间戳以获得更准确的测量结果

调优一般方法

  1. 程序设计良好,易于理解和修改(前提)
  2. 如果性能不佳:
    a. 保存当前状态
    b. 测量,找出时间主要消耗在哪里
    c. 分析问题:是否因为高层设计、数据结构、算法导致的,如果是,返回步骤 1
    d. 如果设计、数据结构、算法没问题,针对上述步骤中的瓶颈进行代码调优
    e. 每进行一项优化,立即进行测量
    f. 如果没有效果,恢复到 a 的状态。(大多数的调优尝试几乎不会对性能产生影响,甚至产生负面影响。代码调优的前提是代码设计良好,易于理解和修改。Code tuning 通常会对设计、可读性、可维护性产生负面影响,如果 tuning 改良了设计或者可读性,那么不应该叫 tuning,而是属于步骤 1)
  3. 重复步骤 2

并发转载

https://www.cnblogs.com/tengzijian/p/a-tour-of-cpp-modern-cpp-concurrency-1.html

多线程/并发

不要把并发当作灵丹妙药:如果顺序执行可以搞定,通常顺序会比并发更简单、更快速!

标准库还在头文件 <future> 中提供了一些机制,能够让程序员在更高的任务的概念层次上工作,而不是直接使用低层的线程、锁

  1. futurepromise:用于从另一个线程中返回一个值
  2. packaged_task:帮助启动任务,封装了 futurepromise,并且建立两者之间的关联
  3. async():像调用一个函数那样启动一个任务。形式最简单,但也最强大!

future 和 promise

futurepromise 可以在两个任务之间传值,而无需显式地使用锁,实现了高效地数据传输。其基本想法很简单:当一个任务向另一个任务传值时,把值放入 promise,通过特定的实现,使得值可以通过与之关联的 future 读出(一般谁启动了任务,谁从 future 中取结果)。

在这里插入图片描述

packaged_task

packaged_task 可以简化任务的设置,关联 future/promisepackaged_task 封装了把返回值或异常放入 promise 的操作,并且调用 packaged_taskget_future() 方法,可以得到一个与 promise 关联的 future

async()

把任务看成是一个恰巧可能和其他任务同时运行的函数

C++ 高性能编程实战转载

https://zhuanlan.zhihu.com/p/533708198

一、整体视角

预置知识 - Cache

Cache line size

CPU 从内存 Load 数据是一次一个 cache line;往内存里面写也是一次一个 cache line,所以一个 cache line 里面的数据最好是读写分开,否则就会相互影响。

Cache associative

全关联(full associative):内存可以映射到任意一个 Cache line;

N-way 关联:这个就是一个哈希表的结构,N 就是冲突链的长度,超过了 N,就需要替换。

Cache type

I-cache(指令)、D-cache(数据)、TLB(MMU 的 cache)

系统优化方法

1. Asynchronous
2.Polling

用于 轮询轮询式检查 外设或资源的状态,通常用于 检查是否有数据可用某个事件是否发生

Polling 是网络设备里面常用的一个技术,比如 Linux 的 NAPI 或者 epoll。与之对应的是中断,或者是事件。Polling 避免了状态切换的开销,所以有更高的性能。

3. 静态内存池

静态内存有更好的性能,但是适应性较差(特别是系统里面有多个 任务的时候),而且会有浪费(提前分配,还没用到就分配了)。

4. 并发优化:lock-free 和 lock-less

lock-free 是完全无锁的设计,有两种实现方式:

Per-CPU 数据(有时也叫 Thread-Local Storage, TLS

CAS based,CAS 是 compare and swap,这是一个原子操作(spinlock 的实现同样需要 compare and swap,但区别是 spinlock 只有两个状态 LOCKED 和 UNLOCKED,而 CAS 的变量可以有多个状态);其次,CAS 的实现必须由硬件来保障(原子操作),CAS 一次可以操作 32 bits,也有 MCAS,一次可以修改一块内存。基于 CAS 实现的数据结构没有一个统一、一致的实现方法,所以有时不如直接加锁的算法那么简单,直接,针对不同的数据结构,有不同的 CAS 实现方法。

lock-less 的目的是减少锁的争用(contention),而不是减少锁。这个和锁的粒度(granularity)相关,锁的粒度越小,等待的时间就越短,并发的时间就越长。

锁的争用,需要考虑不同线程在获取锁后,会执行哪些不同的动作。比如多线程队列,一般情况下,我们一把锁锁住整个队列,性能很差。如果所有的 enqueue 操作都是往队列的尾部插入新节点,而所有的 dequeue 操作都是从队列的头部删除节点,那么 enqueue 和 dequeue 大部分时候都是相互独立的,我们大部分时候根本不需要锁住整个队列,白白损失性能!那么一个很自然就能想到的算法优化方案就呼之欲出了:我们可以把那个队列锁拆成两个:一个队列头部锁(head lock)和一个队列尾部锁(tail lock)

5. 进程间通信 - 共享内存

对于本地进程间需要高频次的大量数据交互,首推共享内存这种方案。

6. I/O 优化 - 多路复用技术

网络编程中,当每个线程都要阻塞在 recv 等待对方的请求,如果访问的人多了,线程开的就多了,大量线程都在阻塞,系统运转速度也随之下降。这个时候,你需要多路复用技术,使用 select 模型,将所有等待(accept、recv)都放在主线程里,工作线程不需要再等待。
在这里插入图片描述

但是,select 不能应付海量的网站访问。这个时候,你需要升级多路复用模型为 epoll。select 有三弊,epoll 有三优:

  • select 底层采用数组来管理套接字描述符,同时管理的数量有上限,一般不超过几千个,epoll使用树和链表来管理,同时管理数量可以很大
  • select不会告诉你到底哪个套接字来了消息,你需要一个个去询问。epoll 直接告诉你谁来了消息,不用轮询
  • select进行系统调用时还需要把套接字列表在用户空间和内核空间来回拷贝,循环中调用 select 时简直浪费。epoll 统一在内核管理套接字描述符,无需来回拷贝
7. 线程池技术

使用一个公共的任务队列,请求来临时向队列中投递任务,各个工作线程统一从队列中不断取出任务来处理,这就是线程池技术。

在这里插入图片描述

多线程技术的使用一定程度提升了服务器的并发能力,但同时,多个线程之间为了数据同步,常常需要使用互斥体、信号、条件变量等手段来同步多个线程。这些重量级的同步手段往往会导致线程在用户态/内核态多次切换,系统调用,线程切换都是不小的开销。

算法优化

代码层次优化

1. I-cache 优化

一是相关的源文件要放在一起;二是相关的函数在object文件里面,也应该是相邻的。这样,在可执行文件被加载到内存里面的时候,函数的位置也是相邻的。相邻的函数,冲突的几率比较小。而且相关的函数放在一起,也符合模块化编程的要求:那就是 高内聚,低耦合。

如果能够把一个 code path 上的函数编译到一起(需要编译器支持,把相关函数编译到一起), 很显然会提高 I-cache 的命中率,减少冲突。但是一个系统有很多个 code path,所以不可能面面俱到。不同的性能指标,在优化的时候可能是冲突的。所以尽量做对所以 case 都有效的优化,虽然做到这一点比较难。

常见的手段有函数重排(获取程序运行轨迹,重排二进制目标文件(elf 文件)里的代码段)、函数冷热分区等。

2. D-cache相关优化

  • Cache line alignment (cache 对齐)

数据跨越两个 cacheline,就意味着两次 load 或者两次 store。如果数据结构是 cacheline 对齐的,就有可能减少一次读写。数据结构的首地址 cache line 对齐,意味着可能有内存浪费(特别是数组这样连续分配的数据结构),所以需要在空间和时间两方面权衡。

  • 分支预测

likely/unlikely

  • Data prefetch (数据预取)

  • Register parameters (寄存器参数)

一般来说,函数调用的参数少于某个数,比如 3,参数是通过寄存器传递的(这个要看 ABI 的约定)。所以,写函数的时候,不要带那么多参数。

  • Lazy computation (延迟计算)

延迟计算的意思是最近用不上的变量,就不要去初始化。通常来说,在函数开始就会初始化很多数据,但是这些数据在函数执行过程中并没有用到(比如一个分支判断,就退出了函数),那么这些动作就是浪费了。

变量初始化是一个好的编程习惯,但是在性能优化的时候,有可能就是一个多余的动作,需要综合考虑函数的各个分支,做出决定。

延迟计算也可以是系统层次的优化,比如 COW(copy-on-write) 就是在 fork 子进程的时候,并没有复制父进程所有的页表,而是只复制指令部分。当有写发生的时候,再复制数据部分,这样可以避免不必要的复制,提供进程创建的速度。

  • Early computation (提前计算)

有些变量,需要计算一次,多次使用的时候。最好是提前计算一下,保存结果,以后再引用,避免每次都重新计算一次。

  • Allocation on stack (局部变量)

内存尽量在栈上分配,不要用堆

  • Per-cpu data structure (非共享的数据结构)

比如并发编程时,给每个线程分配独立的内存空间

  • Move exception path out (把 exception 处理放到另一个函数里面)

只要引入了异常机制,无论系统是否会抛出异常,异常代码都会影响代码的大小与性能;未触发异常时对系统影响并不明显,主要影响一些编译优化手段;触发异常之后按异常实现机制的不同,其对系统性能的影响也不相同,不过一般很明显。所以,不用担心异常对正常代码逻辑性能的影响,同时不要借用异常机制处理业务逻辑。现代 C++ 编译器所使用的异常机制对正常代码性能的影响并不明显,只有出现异常的时候异常机制才会影响整个系统的性能。

另外,把 exception path 和 critical path 放到一起(代码混合在一起),就会影响 critical path 的 cache 性能。而很多时候,exception path 都是长篇大论,有点喧宾夺主的感觉。如果能把 critical path 和 exception path 完全分离开,这样对 i-cache 有很大帮助

  • Read, write split (读写分离)

伪共享(false sharing):就是说两个无关的变量,一个读,一个写,而这两个变量在一个cache line里面。那么写会导致cache line失效(通常是在多核编程里面,两个变量在不同的core上引用)。读写分离是一个很难运用的技巧,特别是在code很复杂的情况下。需要不断地调试,是个力气活(如果有工具帮助会好一点,比如 cache miss时触发 cpu 的 execption 处理之类的)

瓶颈定位手段

软件性能瓶颈定位的常用手段有 perf(火焰图)以及在 Intel CPU 上使用 pmu-tools 进行 TopDown 分析**(TMAM(Top-down Micro-architecture Analysis Methodology,自顶向下的微架构分析方法))**。
在这里插入图片描述在这里插入图片描述

二、并发优化

单线程中的并发

1.SIMD 指令集优化

实际上单核单线程内也能利用上硬件细粒度的并发能力:SIMD(Single Instruction Multiple Data),与之相对的就是多核多线程中的 MIMD(Multiple Instruction Multiple Data)。CPU 指令集的发展经历了 MMX(Multi Media eXtension)、SSE(Streaming SIMD Extensions)、AVX(Advanced Vector Extensions)、IMCI 等。

C/C++指令集介绍以及优化(主要针对SSE优化)

https://zhuanlan.zhihu.com/p/325632066

2.OoOE(Out of Ordered Execution)优化

经典 5 级 RISC 流水线如下图所示,分为 5 个步骤:取指 -> 译码 -> 计算 -> 访存 -> 写回。

在这里插入图片描述

当执行环节遇到数据依赖,以及缓存未命中等场景,就会导致整体停顿的产生,其中 MEM 环节的影响尤其明显,主要是因为多级缓存及多核共享带来的单次访存所需周期数参差不齐的现象越来越严重。为了减轻停顿的影响,现代 CPU 引入了乱序执行结合超标量的技术,一方面:对于重点执行部件,比如计算、访存,增加多份来支持并行;另一方面:在执行部件前引入缓冲池/队列机制。最终从流水线模式向类似"多线程"的方式靠拢。

3.TMAM(Top-down Micro-architecture Analysis Methodology,自顶向下的微架构分析方法)

TMAM 理论基础就是将各类 CPU 微指令进行归类从大的方面先确认可能出现的瓶颈,再进一步分析找到瓶颈点,该方法也符合我们人类的思维,从宏观再到细节,过早的关注细节,往往需要花费更多的时间。这套方法论的优势在于:

  • 即使没有硬件相关的知识也能够基于 CPU 的特性优化程序
  • 系统性的消除我们对程序性能瓶颈的猜测:分支预测成功率低?CPU 缓存命中率低?内存瓶颈?
  • 快速的识别出在多核乱序 CPU 中瓶颈点
  • Intel 提供分析工具:pmu-tools

TMAM 将各种 CPU 资源大致分为 4 类:

在这里插入图片描述

1.Retiring

Retiring 表示运行有效的 uOps 的 pipeline slot,即这些 uOps 最终会退出(注意一个微指令最终结果要么被丢弃、要么退出将结果回写到 register),它可以用于评估程序对 CPU 的相对比较真实的有效率。理想情况下,所有流水线 slot 都应该是"Retiring"。100% 的 Retiring 意味着每个周期的 uOps Retiring数将达到最大化,极致的 Retiring 可以增加每个周期的指令吞吐数(IPC)。需要注意的是,Retiring 这一分类的占比高并不意味着没有优化的空间。例如 retiring 中 Microcode assists 的类别实际上是对性能有损耗的,我们需要避免这类操作。

2.Bad Speculation

Bad Speculation 表示错误预测导致浪费 pipeline 资源,包括由于提交最终不会 retired 的 uOps 以及部分 slots 是由于从先前的错误预测中恢复而被阻塞的。由于预测错误分支而浪费的工作被归类为"错误预测"类别。例如:if、switch、while、for等都可能会产生 bad speculation。

优化建议:

(1)在使用if的地方尽可能使用gcc的内置分支预测特性

#define likely(x) __builtin_expect(!!(x), 1) //gcc内置函数, 帮助编译器分支优化
#define unlikely(x) __builtin_expect(!!(x), 0)if(likely(condition)) {// 这里的代码执行的概率比较高}if(unlikely(condition)) {// 这里的代码执行的概率比较高}// 尽量避免远调用

(2)避免间接跳转或者调用

在c++中比如switch、函数指针或者虚函数在生成汇编语言的时候都可能存在多个跳转目标,这个也是会影响分支预测的结果,虽然BTB可改善这些但是毕竟BTB的资源是很有限的。

3.Front-End-Bound
  • 取指令
  • 将指令进行解码成微指令
  • 将指令分发给 Back-End,每个周期最多分发4条微指令

Front-End Bound 表示处理的 Front-End 的一部分 slots 没法交付足够的指令给 Back-End。Front-End 作为处理器的第一个部分其核心职责就是获取 Back-End 所需的指令。在 Front-End 中由预测器预测下一个需要获取的地址,然后从内存子系统中获取对应的缓存行,在转换成对应的指令,最后解码成uOps(微指令)。Front-End Bound 意味着,会导致部分slot 即使 Back-End 没有阻塞也会被闲置。例如因为指令 cache misses引起的阻塞是可以归类为 Front-End Bound。

优化建议:

(1)尽可能减少代码的 footprint:C/C++可以利用编译器的优化选项来帮助优化,比如 GCC -O* 都会对 footprint 进行优化或者通过指定 -fomit-frame-pointer 也可以达到效果;

**(2)充分利用 CPU 硬件特性:**宏融合(macro-fusion)特性可以将2条指令合并成一条微指令,它能提升 Front-End 的吞吐。

(3)调整代码布局(co-locating-hot-code)

  • 充分利用编译器的 PGO 特性:-fprofile-generate -fprofile-use
  • 可以通过__attribute__ ((hot)) __attribute__ ((code)) 来调整代码在内存中的布局,hot 的代码在解码阶段有利于 CPU 进行预取。

(4)分支预测优化

  • 消除分支可以减少预测的可能性能:比如小的循环可以展开比如循环次数小于64次(可以使用GCC选项 -funroll-loops)
  • 尽量用if 代替:? ,不建议使用a=b>0? x:y 因为这个是没法做分支预测的
  • 尽可能减少组合条件,使用单一条件比如:if(a||b) {}else{} 这种代码CPU没法做分支预测的
  • 对于多case的switch,尽可能将最可能执行的case 放在最前面
  • 我们可以根据其静态预测算法投其所好,调整代码布局,满足以下条件:前置条件,使条件分支后的的第一个代码块是最有可能被执行的
4.Back-End-Bound
  • 接收 Front-End 提交的微指令
  • 必要时对 Front-End 提交的微指令进行重排
  • 从内存中获取对应的指令操作数
  • 执行微指令、提交结果到内存

Back-End Bound 表示部分 pipeline slots 因为 Back-End 缺少一些必要的资源导致没有 uOps 交付给 Back-End。

Back-End 处理器的核心部分是通过调度器乱序地将准备好的 uOps 分发给对应执行单元,一旦执行完成,uOps 将会根据程序的顺序返回对应的结果。例如:像 cache-misses 引起的阻塞(停顿)或者因为除法运算器过载引起的停顿都可以归为此类。此类别可以在进行细分为两大类:Memory-Bound 、Core Bound。

优化建议:

(1)调整算法减少数据存储,减少前后指令数据的依赖提高指令运行的并发度

(2)根据cache line调整数据结构的大小

(3)避免L2、L3 cache伪共享

归纳总结一下就是:

Front End Bound = Bound in Instruction Fetch -> Decode (Instruction Cache, ITLB)

Back End Bound = Bound in Execute -> Commit (Example = Execute, load latency)

Bad Speculation = When pipeline incorrectly predicts execution (Example branch mispredict memory ordering nuke)

Retiring = Pipeline is retiring uops

一个微指令状态可以按照下图决策树进行归类:

在这里插入图片描述

上图中的叶子节点,程序运行一定时间之后各个类别都会有一个 pipeline slot 的占比,只有 Retiring 的才是我们所期望的结果,那么每个类别占比应该是多少才是合理或者说性能相对来说是比较好,没有必要再继续优化?Intel 在实验室里根据不同的程序类型提供了一个参考的标准:

在这里插入图片描述

只有 Retiring 类别是越高越好,其他三类都是占比越低越好。如果某一个类别占比比较突出,那么它就是我们进行优化时重点关注的对象。

使用示例

https://segmentfault.com/a/1190000039650181

多线程中的并发

1.临界区保护技术
  • Mutual Execlusion(pessimistic locking):基本的互斥技术,如果多个线程竞争同一个锁,存在某个时间周期,算法没有任何实质进展,典型的悲观锁算法
  • Lock Free (optimistic locking):因为冲突组成算法的一个线程没有任何实质进展,基于 CAS 同步提交,若遇到冲突,回滚
  • Wait Free:任意时间周期,算法的任意一个线程都有实质进展

**多线程累加,**上述三种技术对应以下实现方案:

  • 上锁后累加:简单易懂,但会引入锁的开销和潜在的阻塞。

  • 累加后 CAS 提交:无锁的实现,使用 CAS 确保线程安全,适用于高并发场景。

  • 累加后 FAA:使用 Fetch and Add 原子操作,简洁且性能优越,适合频繁的累加操作。

  • 优先考虑 Wait Free 的方法,如果可以的话,在性能上接近完全消除了临界区的效果

  • 充分缩减临界区

  • 在临界区足够小,且无 Wait Free 方案时,不必对 Lock Free 过度执着,因为 Lock Free “无效预测执行” 以及支持撤销回滚的两阶段提交算法非常复杂,反而会引起过多的消耗。锁本身的开销虽然稍重于原子操作,但其实可以接受的。真正影响性能的是临界区被迫串行执行所带来的并行能力折损。

2.并发队列
3.伪共享

三、内存优化

内存分配器tcmalloc 和 jemalloc,分别来自 google 和 facebook

针对多线程竞争的角度,tcmalloc 和 jemalloc 共同的思路是引入线程缓存机制。通过一次从后端获取大块内存,放入缓存供线程多次申请,降低对后端的实际竞争强度。主要不同点是,当线程缓存被击穿后,tcmalloc 采用了单一的 page heap(简化了中间的 transfer cache 和 central cache) 来承载;而 jemalloc 采用了多个 arena(甚至超过了服务器 core 数)来承载。一般来讲,在线程数较少,或释放强度较低的情况下,较为简洁的 tcmalloc 性能稍胜 jemalloc。在 core 数较多、申请释放频繁时,jemalloc 因为锁竞争强度远小于 tcmalloc,性能较好。

在这里插入图片描述

malloc 的核心要素,也就是竞争性和连续性。

线程池技术中,每个线程各司其职,完成一个一个的任务。在 malloc 看来,就是多个长生命周期的线程,随机的在各个时间节点进行内存申请和内存释放。基于这样的场景,首先,尽量分配连续地址空间。其次,多线程下需要考虑分区隔离和减少竞争。

多态内存资源(Polymorphic Memory Resource)

std::pmr::memory_resource 是 C++17 引入的多态内存资源(Polymorphic Memory Resource)的核心概念,属于 C++ 标准库中的内存管理机制。它允许开发者使用不同的内存分配策略,以提高内存管理的灵活性和性能。

PMR 将内存的分配与内存管理解耦,memory_resource 负责内存的管理,而具体的内存分配则可以通过不同的资源实现,从而提供更大的灵活性。

使用示例

https://zhuanlan.zhihu.com/p/96089089

内存分配的具体策略,可以有14种之多哦!

在这里插入图片描述

C++性能优化转载

https://www.cnblogs.com/qiangz/p/17085951.html

__builtin_prefetch()数据预读

为了降低内存读取的cache-miss延迟,可以通过gcc提供的这个内置函数来预读数据。当知道数据的内存地址即将要被读取(在下一个load & store指令到来之前),在数据被处理之前,就可以在代码中通过指令通知目标,去读取数据并放到缓存中。

需要注意,软件预取是有代价的:我们的系统执行了更多的指令,并将更多的数据加载到缓存中,这在某些情况下可能会导致其他方面的性能下降。

for (int i = 0; i < n; i++) {__builtin_prefetch(&array[i + 16], 0, 1);  // 预取未来第16项sum += array[i];  // 实际访问当前项
}

likely和unlikely分支预测

通过先验概率提示编译器和CPU,提高分支预测准确率,降低预测错误带来的性能损失。

likely,用于修饰if/else if分支,表示该分支的条件更有可能被满足。而unlikely与之相反。

#define likely(x) __builtin_expect(!!(x), 1) 
#define unlikely(x) __builtin_expect(!!(x), 0)

添加分支预测后,可以增加分支预测正确性,让更大概率走到的分支对应指令顺序排放在后面,可以减少jmp指令的调用,有助于提高性能。

C++语言层面

1. 使用const引用传递而非值传递

2. for循环中使用引用遍历

3. 注意隐式转换带来的拷贝

隐式转换,创建了临时对象。

4. 定义即初始化

一次默认构造函数,一次赋值运算符函数》》》》》》》一次拷贝构造函数

5. 循环中复用临时变量

6. 尽量使用复合运算符

复合运算符实现的形式会返回自身的引用。

7. 在构造函数中使用初始化列表

构造时即初始化。

8. 使用std::move()避免拷贝

9. 定义移动构造函数、移动赋值运算符函数或右值引用函数

10. 利用好Copy Elision

在C++17以后,编译期默认启用RVO(Return Value Optimization),不会对函数返回的局部变量值进行拷贝,直接在函数调用处进行构造,只要满足以下两个条件其一:

  • URVO(Unnamed Return Value Optimization):函数的各分支都返回同一个类型的匿名变量。
  • NRVO(Named Return Value Optimization):函数的各分支都返回同一个非匿名变量。

11. 容器预留空间

12. 容器内原地构造

容器类型emplace_back()emplace()说明
std::vector✅ 支持✅ 支持在尾部原地构造元素,效率高
std::deque✅ 支持✅ 支持尾部或头部插入均支持
std::list✅ 支持emplace() 可插入任意位置
std::forward_list✅ 支持单向链表,只能 emplace_after()
std::array固定大小,不支持动态插入
std::set / std::multiset✅ 支持emplace() 插入有序元素
std::map / std::multimap✅ 支持用于插入 pair<key, value>
std::unordered_set✅ 支持无序容器插入优化
std::unordered_map✅ 支持通常 emplace(k, v)
std::stack / std::queue✅(包装容器支持)取决于底层容器(如 deque
std::priority_queue✅(包装容器支持)底层容器支持则支持

13. 容器存储指针代替对象拷贝

14. 常量集合定义添加static

15. 减少重复查找和判断

16. 利用好constexpr编译期计算

矩阵乘法

优化手段:改进运算顺序、SIMD、循环展开(减少循环控制(如跳转、比较)开销,暴露更多指令重排空间)、循环分块(A0B0 加载进 cache、等 C0 运算完,再写回内存,避免 cache line 被反复换入换出)、多线程

改变运算顺序的区别:https://blog.csdn.net/LxXlc468hW35lZn5/article/details/126912933

矩阵乘法优化过程(DGEMM):https://zhuanlan.zhihu.com/p/76347262

高并发

大流量、大规模业务请求的场景,比如春运抢票、电商“双十一”抢购,秒杀促销等。

高并发的衡量指标主要有两个:一是系统吞吐率,比如 QPS(每秒查询率)、TPS(每秒事务数)、IOPS(每秒磁盘进行的 I/O 次数)。另外一个就是时延,也就是从发出 Reques 到收到 Response 的时间间隔。一般而言,用户体验良好的请求、响应系统,时延应该控制在 250 毫秒以内。

高并发的设计思路

垂直方向:提升单机能力

最大化单台服务器的性能,主要有两个手段:一是提升硬件能力,比如采用核数更多、主频更高的服务器,提升网络带宽,扩大存储空间;二是软件性能优化,尽可能榨干 CPU 的每一个 Tick。

水平方向:分布式集群

为了降低分布式软件系统的复杂性,一般会用到“架构分层和服务拆分”,通过分层做“隔离”,通过微服务实现“解耦”,这样做的一个主要目的就是为了方便扩容。然而一味地加机器扩容有时也会带来额外的问题,比如系统的复杂性增加、垂直方向的能力受限等。

高并发的关键技术

层次划分(垂直方向) + 功能划分(水平方向)

负载均衡

方式描述优缺点
DNS 负载均衡根据域名返回多个 IP,客户端随机或就近访问简单、但受限于缓存更新速度慢
硬件负载均衡使用专业设备(如 F5)分发请求性能强、成本高
软件负载均衡NGINX、LVS 分发请求成本低、灵活、易部署

数据库层面:分库分表 + 读写分离

技术What(做什么)Why(为什么)How(怎么做)
分库分表拆分成多个数据库/数据表减轻单库单表压力按用户 ID、时间等进行 hash 或范围划分
读写分离读请求走从库,写请求走主库提升读并发能力MySQL 主从复制 + 应用层路由

读多写少:缓存

类型应用场景实现方式案例
本地缓存单节点、高频访问Guava Cache, Caffeine用户最近访问记录
分布式缓存多节点共享数据Redis, Memcached商品详情页、排行榜、热点文章等
缓存策略控制数据生命周期LRU、TTL、双写一致性、Cache Aside读写流程设计应考虑缓存一致性问题

消息中间件(Why:系统间解耦 & 异步)

作用举例常用中间件
削峰填谷秒杀场景中先放入 MQ 排队Kafka, RabbitMQ
解耦微服务用户注册后触发多个动作用户注册 → MQ → 发送邮件、积分服务等
重试机制避免失败操作立即失败消息可设置重试策略

流控:限制流量/控制优先级,避免雪崩效应

策略描述
Reject 策略达上限立即拒绝
Queue 策略达上限排队
Token Bucket限流 + 平滑
Leaky Bucket匀速出水

高并发实践

维度Why(目的)What(做法)How(常用手段)
1. 单机性能优化榨干单台服务器性能代码优化 + 系统优化内存优化、锁优化、零拷贝、epoll、线程池
2. 架构设计优化让系统可以水平扩展分层、拆分、解耦、异步微服务、消息队列、限流、负载均衡、缓存、CDN
3. 流量治理控制压力、防止系统被打爆限流、降级、熔断Sentinel、Hystrix、RateLimiter、令牌桶、漏桶算法
4. 数据层优化避免 DB 成为性能瓶颈分库分表、读写分离、缓存、索引Redis 缓存、MySQL 主从、ElasticSearch、异步写入

实践经验

1️⃣ 接口层:网关 + 限流 + 缓存
实践点说明
API网关限流防止恶意访问或突发流量压垮后端,可设 QPS 阈值
静态内容走 CDN图片/视频/JS 等资源,不占服务带宽和 CPU
GET 接口缓存查询接口缓存结果,避免频繁访问数据库
异步响应返回结果慢的操作(如导出/视频转码)用异步任务队列

2️⃣ 服务层:拆分 + 异步 + 降级 + 熔断
实践点说明
微服务拆分把大服务拆成小服务,按功能部署,避免“大而全”模块互相拖慢
MQ 解耦 + 削峰秒杀、下单等请求先入队列,后端慢慢消费,防止瞬时打挂数据库
降级处理非核心功能(如推荐/广告)出错时不影响主业务(如下单)
熔断保护某服务异常时断开调用链,防止雪崩扩散

3️⃣ 数据层:缓存 + 索引 + 分表分库
实践点说明
热数据进缓存用 Redis 缓存高频访问的数据(比如用户 Token / 商品详情)
索引优化查询慢的 SQL 通常缺少合适索引,尤其是多表 JOIN / 排序字段
分表分库单表数据量大于千万时影响性能,可根据用户ID或时间做分片
读写分离写入主库、读取从库,提高读吞吐能力

4️⃣ 系统层:连接池 + 多线程 + IO模型
实践点说明
数据库连接池用如 HikariCP、Druid 等连接池,避免频繁建立/关闭连接
线程池复用用线程池处理请求,避免频繁创建线程
IO 多路复用使用 epoll / select 提高并发连接处理能力(如高性能 Web Server)

5️⃣ 其他高并发常用技巧
技巧名称作用
幂等性设计保证请求重复不会重复处理(如重复下单)
雪崩保护关键缓存设置过期时间随机 + 本地缓存备份
限流算法令牌桶、漏桶算法,控制每秒请求量不超过阈值
热点预热大促前将热门商品、广告位数据预加载到缓存中
多线程 + IO模型
实践点说明
数据库连接池用如 HikariCP、Druid 等连接池,避免频繁建立/关闭连接
线程池复用用线程池处理请求,避免频繁创建线程
IO 多路复用使用 epoll / select 提高并发连接处理能力(如高性能 Web Server)

5️⃣ 其他高并发常用技巧
技巧名称作用
幂等性设计保证请求重复不会重复处理(如重复下单)
雪崩保护关键缓存设置过期时间随机 + 本地缓存备份
限流算法令牌桶、漏桶算法,控制每秒请求量不超过阈值
热点预热大促前将热门商品、广告位数据预加载到缓存中

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

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

相关文章

frida简介及环境搭建

frida简介及环境搭建 一、frida简介二、frida环境搭建一、frida简介 frida是一款轻量级的Hook框架,也可以说是一种动态插桩工具,可以插入一些原生代码到原生app的内存空间去,动态地监视和修改器行为,这些原生平台可以是Win、Mac、Linux、Android或者iOS。 frida分为两个部…

Python实例题:Python计算微积分

目录 Python实例题 题目 代码实现 实现原理 符号计算&#xff1a; 数值计算&#xff1a; 可视化功能&#xff1a; 关键代码解析 1. 导数计算 2. 积分计算 3. 微分方程求解 4. 函数图像绘制 使用说明 安装依赖&#xff1a; 基本用法&#xff1a; 示例输出&#…

Mybatis 拦截器 与 PageHelper 源码解析

Mybatis 拦截器 与 PageHelper 源码解析 一、MyBatis插件机制的设计思想二、Interceptor接口核心解析2.1 核心方法2.2 Intercepts、Signature 注解2.3 自定义拦截器 三、PageHelper 介绍3.1 使用姿势3.2 参数与返回值3.3 使用小细节 四、PageHelper 核心源码解析4.1 分页入口&a…

Linux中 SONAME 的作用

🧠 一、从 -lexample 到 SONAME ✅ 假设你有以下文件结构: /libexample.so → libexample.so.1 /libexample.so.1 → libexample.so.1.0.0 /libexample.so.1.0.0 # SONAME: libexample.so.1/libexample.so.2 → libexample.so.2.0.0 /libexample.so.2.0…

热门消息中间件汇总

文章目录 前言RabbitMQ基本介绍核心特性适用场景 Kafka基本介绍核心特性适用场景 RocketMQ基本介绍核心特性适用场景 NATS基本介绍核心特性适用场景 总结选型建议与未来趋势选型建议未来趋势 结语 前言 大家后&#xff0c;我是沛哥儿。作为技术领域的老湿机&#xff0c;在消息…

【DAY42】Grad-CAM与Hook函数

内容来自浙大疏锦行python打卡训练营 浙大疏锦行 知识点: 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 在深度学习中&#xff0c;我们经常需要查看或修改模型中间层的输出或梯度。然而&#xff0c;标准的前向传播和反…

C++032(static变量)

static变量 static变量是静态存储变量&#xff0c;定义变量时系统就会为其分配固定的存储单元&#xff0c;直至整个程序运行结束。之前我们接触过的全局变量即为static变量&#xff0c;它们存放在静态存储区中。使用static关键字&#xff0c;可将变量声明成static变量。例如&a…

N元语言模型 —— 一文讲懂!!!

目录 引言 一. 基本知识 二.参数估计 三.数据平滑 一.加1法 二.减值法/折扣法 ​编辑 1.Good-Turing 估计 ​编辑 2.Back-off (后备/后退)方法 3.绝对减值法 ​编辑4.线性减值法 5.比较 三.删除插值法(Deleted interpolation) 四.模型自适应 引言 本章节讲的…

SpringAI Alibaba实战文生图

1️⃣ 前置准备&#xff1a;搭建开发环境与服务配置&#x1f680; &#x1f527; 1.1 环境要求 JDK 17&#xff08;推荐 JDK 21&#xff09;、Spring Boot 3.x&#xff08;本案例使用 3.3.4&#xff09;、阿里云百炼大模型服务 API Key。需在阿里云控制台完成服务开通并获取有…

实战二:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…

Kotlin List 操作全面指南

在传统 Java 开发 List 相关的 API 中&#xff0c;有着样板代码冗长、缺乏链式调用、空安全等问题。 Kotlin 这门语言 为 List 提供了丰富的扩展函数&#xff0c;这些函数大大简化了集合操作&#xff0c;解决了传统 Java 集合 API 中的许多痛点。 一、基础操作 1. 创建 List …

硬盘寻址全解析:从 CHS 三维迷宫到 LBA 线性王国

在数字存储的底层世界&#xff0c;硬盘如同一个巨大的 “数据图书馆”&#xff0c;而寻址模式就是决定如何高效找到 “书籍”&#xff08;扇区&#xff09;的核心规则。从早期基于物理结构的 CHS&#xff08;柱面 - 磁头 - 扇区&#xff09;三维寻址&#xff0c;到现代抽象化的…

oracle 11g ADG备库报错ORA-00449 lgwr unexpectedly分析处理

问题背景 昨天遇到群友提问&#xff0c;遇到ADG备库挂了的情况 数据版本:11.2.0.4 操作系统:Centos7.9 环境&#xff1a;ADG主备库&#xff0c;主库为RAC&#xff0c;备库也是RAC 具体报错ORA-00449以及ORA-04021 看样子是LGWR挂了&#xff0c;还有个锁等待。 问题分析 先…

Python——day46通道注意力(SE注意力)

一、 什么是注意力 注意力机制是一种让模型学会「选择性关注重要信息」的特征提取器&#xff0c;就像人类视觉会自动忽略背景&#xff0c;聚焦于图片中的主体&#xff08;如猫、汽车&#xff09;。 transformer中的叫做自注意力机制&#xff0c;他是一种自己学习自己的机制&…

入门AJAX——XMLHttpRequest(Post)

一、前言 在上篇文章中&#xff0c;我们已经介绍了 HMLHttpRequest 的GET 请求的基本用法&#xff0c;并基于我提供的接口练习了两个简单的例子。如果你还没有看过第一篇文章&#xff0c;强烈建议你在学习完上篇文章后再学习本篇文章&#xff1a; &#x1f517;入门AJAX——XM…

​BEV和OCC学习-3:mmdet3d 坐标系

目录 坐标系 转向角 (yaw) 的定义 框尺寸的定义 与支持的数据集的原始坐标系的关系 KITTI Waymo NuScenes Lyft ScanNet SUN RGB-D S3DIS 坐标系 坐标系 — MMDetection3D 1.4.0 文档https://mmdetection3d.readthedocs.io/zh-cn/latest/user_guides/coord_sys_tuto…

Redis高可用架构

概述 Redis作为常用的缓存中间件&#xff0c;因其高性能&#xff0c;丰富的数据结构&#xff0c;使用简单等&#xff0c;常被用在需要一定高性能的To C业务场景中&#xff0c;如「秒杀场景」「用户信息中心」「帖子」「群聊」等等大家常见的业务场景中&#xff0c;以提高服务的…

使用WPF的Microsoft.Xaml.Behaviors.Wpf中通用 UI 元素事件

Nuget下载之后记得要先引用下面的 xmlns:i"http://schemas.microsoft.com/xaml/behaviors" <!-- 鼠标事件 --> <i:EventTrigger EventName"MouseEnter"/> <!-- 鼠标进入 --> <i:EventTrigger EventName"MouseLeave"/&g…

敏捷开发中如何避免过度加班

在敏捷开发过程中避免过度加班&#xff0c;需要明确敏捷原则、合理规划迭代任务、加强团队沟通、优化流程效率、设定合理的工作负荷、注重团队士气和成员健康。明确敏捷原则&#xff0c;即保证可持续发展的步调&#xff0c;避免频繁地变更需求、过度承诺任务量。合理规划迭代任…

JSON解析崩溃原因及解决方案

问题记录&#xff1a; /************************************************| * 描述: 将ID124执行NFC操作-JSON解析为结构体* 函数名: cJSON_ID124_to_struct* 参数[ I]: *json_string 待解析的指针* 参数[II]: *wireless_rxd 结构体指针* 返回: 成功返回0 失…