性能优化

性能建模

在软件设计阶段做好性能的评估分析,通过一定的方法提前识别出软件设计中潜在的性能瓶颈,并指导优化设计

软件执行模型:

graph LR

A((基本节点 t1)) --> B((循环节点 n))
B --> C((扩展节点))
C --> D((分支选择节点 t2))
C --> E((执行子图))
E --> F((分支节点 t3))
E --> G((分支节点 t4))
B --> H((循环体结束))
H --> B
A --> I((并行节点))
I --> J((基本节点 t5))
I --> K((基本节点 t6))
I --> L((基本节点 t7))
A --> M((基本节点 t8))

基于 QNM 模型的系统执行模型:

graph LR
A[到达过程] --> B[排队区]
B -- 服务请求 --> C[服务中心1]
B -- 服务请求 --> D[服务中心2]
C -- 完成服务 --> E[离开系统]
D -- 完成服务 --> E
C -- 服务请求 --> B
D -- 服务请求 --> B
E --> F[选择逻辑]
F -- 直接退出 --> G[离开系统]
F -- 排队访问磁盘 --> H[排队区]
H -- 服务请求 --> I[服务中心3]
I -- 完成服务 --> E
H -- 服务请求 --> B

并行计算

graph LR
A[结构数据] --> B[并行执行单元]
C[计算逻辑] --> B
B --> D[交互同步]
E[内存] --> D
F[互斥量] --> D
G[消息队列] --> D
H[数据库] --> D

并行执行单元的粒度可大可小,像函数、routine(协程)、actor、线程、进程、作业等。根据处理的特定领域问题,选择合适的并行执行单元粒度,并选择或定制实现相应的并发调度框架

并行架构模式

任务线性分解架构

触发的业务计算逻辑之间相互独立时,我们就可以通过创建多个并行执行单元,分别处理拆分后的不同子问题,并根据不同单元业务工作量的大小,建立与具体硬件线程的映射绑定关系

任务分治架构

针对不能在系统运⾏前完成任务的拆分,而是需要动态创建任务,并借助任务队列来管理执⾏任务。这里的执⾏线程可以从队列中拉取任务,映射到硬件线程上执⾏

数据⼏何分解架构

相同计算逻辑需要在不同的数据上进⾏运算

递归数据架构

针对在遍历的过程中动态创建任务,然后对每个中间计算单元的运算结果逐步合并,计算得到最终的结果

数据流交互架构

⼀个计算单元的输出刚好是另外⼀个计算单元的输⼊,并且消息交互是单向确定性的;业务场景中还会源源不断接收到新的输⼊,需要使⽤相似的计算策略进行处理,设计的核⼼就是如何⾼效实现并发计算单元间的信息交互

异步交互架构

同⼀个任务需要与多个任务进⾏消息交互;同⼀个消息需要多个任务进⾏处理。

性能模式

快速通道模式

找到系统中频繁使用的典型场景,然后针对性地提供定制化方案来优化性能

并行分解模式

批处理模式

这种模式会降低可靠性,处理失败时所造成的影响通常也会比较大,如果突然断电,会造成阶段计算结果丢失

弹性时间模式

针对资源拥塞的情况下,处理时延会增加,通过离散化业务的请求时间,从而避免系统中的单个软件服务和硬件服务出现拥塞的情况

预计算模式

通过空间换时间,将计算逻辑提前到编译期执行,来减少运行期的时间开销

耦合模式

做一件事情的时候,顺带把其他事情一并处理掉,就像一个接口返回多个业务场景需要的信息

搬移计算模式

把计算逻辑从关键路径搬移到非关键路径,从而降低实时处理侧的时延

丢弃模式

把优先级比较低的代码块放到业务最后,在极端场景下,也可以通过直接丢弃不处理来保证系统性能不恶化

并发框架

基准测试

性能场景

分类:

微基准测试

挑战:

  1. JIT的优化:需要使用充足的代码预热,避免JIT造成执行结果的偏差
  2. 测量时间的精度:微尺度的计算机的时间是不准的,一种可行方式,就是迭代、累积运行多次后获取的测试时间间隔,然后再平均到每一次的运行时间上,这样就可以减少获取的时间间隔误差对测量结果的影响
  3. 环境造成的结果波动:必须客观接受获取的测量结果存在波动的这种现象,通过多次测试,并剔除极端值,只选择置信区间内的测量结果进行分析

实施步骤:

  1. 保证测试环境与真实产品环境一致
  2. 合理选择被测方法,性能影响比较大的关键方法进行测试才更划算,同时执行时间越短的方法,越难测试
  3. 编写微基准测试用例
  4. 执行测试,得到数据,分析数据

宏基准测试

挑战:

调优方法论

潜在的性能瓶颈点:

  1. 串行资源受限:串行资源是有限的,随着业务请求量增加,当资源使用饱和后,会导致请求处理吞吐量到达峰值后就不能再提升了
  2. 缓冲类资源消息溢出:如果缓冲区设置过小,当上游请求到达峰值时,可能会造成部分请求被阻塞或丢弃,影响到业务性能
  3. 缓存命中率过低
  4. 软件bug
graph TB
A[业务模型分析] --> B[验证分析正确性]
B --> C[软件架构分析]
C --> D[进一步验证分析] 
D --> E[软件内部分析]
E --> F[识别性能瓶颈点]
F --> G[深入组件/服务实现分析]
G --> H[识别导致性能问题的资源]
  1. 寻找到引起业务性能问题的业务触发点,并验证分析的正确性
  2. 根据组件或服务的接口交互关系寻找触发性能问题的具体组件或服务
  3. 进行软件内部分析,深入到组件/服务的实现层面,识别性能瓶颈点,并准确识别导致性能问题的软硬件资源

性能调优的步骤:

  1. 系统性的性能优化分析诊断:通过自顶向下的分析方法,识别出导致性能劣化的可优化点,包括软件设计和实现等方面的优化点。
  2. 分析调整性能调优目标值:针对识别出的性能优化点,分析修改后的性能提升收益,评估每个优化点的调优目标。
  3. 按照成本收益逐步实施性能调优:根据优化点的优先级排序,逐步进行修改并验证优化效果。考虑性能收益大小、修改工作量和对软件质量的影响等因素。
  4. 增加完善性能基线测试:在性能调优合入后,同步修改和完善性能基线测试,以确保对软件系统性能的监控和看护。

性能调优反模式:

  1. 性能调优严重破坏了软件的质量
  2. 盲目修改代码来尝试优化
  3. 在业务的非性能瓶颈点上反复调优

基础设施优化

CPU

由于 CPU 多级缓存架构的存在,缓存读取命中缓存能带来很大的性能提升,所以代码优化目标是提升 CPU 缓存的命中率

提升数据缓存命中率:

  1. 空间局部性原理
  2. CPU Cache Line

提升指令缓存命中率:

  1. 分支预测

提升多核 CPU 下的缓存命中率:

  1. 进程在不同CPU下切换导致的缓存缺失问题,将进程或者线程绑定到某一颗 CPU 上运行,可以解决这个问题

内存池

  1. 不同内存分配器(TCMalloc、Ptmalloc2)适合不同的场景(分配单元大小、是否并发环境)
  2. 栈上分配的内存相比堆内分配的内存,申请效率高,回收也容易

IO

网络优化

epoll

网络报文到达后,内核就产生了读、写事件,epoll 函数使得进程可以高效地收集到这些事件,通过快速、及时、均等地执行所有事件,异步 Server 实现了高并发

graph TB
A[应用程序] --调用--> B(epoll_wait)
B --返回事件列表--> C[事件循环]
C --遍历事件列表--> D{事件类型}
D --新连接事件--> E[建立新连接]
D --读事件--> F[读取数据]
D --写事件--> G[发送数据]
D --关闭事件--> H[关闭连接]