这是我参与「第五届青训营 」笔记创作活动的第5天
性能优化
什么是性能优化?
提升软件系统处理能力,减少不必要的消耗
为什么做性能优化?
带来用户体验的提升;资源的高效利用也能够降低成本提高效率
性能优化层面
从上到下为
- 业务代码
- 针对特定场景,具体问题,具体分析
- 容易获得较大性能收益
- SDK
- 基础库
- 语言运行时
- 解决更通用的性能问题
- 考虑更多场景
- Tradeoffs
- OS
用数据驱动进行优化是关键
- 自动化性能分析工具——pprof
- 依靠数据而非猜测
- 首先优化最大瓶颈
自动内存管理
是由程序语言的运行时系统管理动态内存,避免了手动内存管理,专注于实现业务逻辑。保证内存使用的正确性和安全性。
GC基本概念
- Mutator:业务线程,分配新对象,修改对象指向关系
- Collector:GC线程,找到存活对象,回收死亡对象的内存空间
- Serial GC:只有一个collector
- 运行时暂停所有Mutator,执行一个collector
- Parallel GC:支持多个collector同时回收的GC算法
- Concurrent GC:mutator和collector可以同时执行
- Collector必须感知对象指向关系的改变
评价GC算法的要点
安全性,吞吐率,暂停时间,内存开销
追踪垃圾回收
对象被回收的条件:指针指向关系不可达的对象
回收被分为三步骤:
- 标记跟对象
- 静态变量,全局变量,常量,线程栈
- 标记:找到可达对象
- 求指针指向关系的传递闭包:从根对象出发,找到所有可达对象
- 清理:所有不可达对象
- 将存活对象复制到另外的内存空间
- 将死亡对象的内存标记为可分配
- 移动并整理存活对象
- 清理:所有不可达对象 生命周期生命周生命周期 根据对象的声明周期,使用不同的标记和清理策略
分代GC
- 每个对象都有年龄:年龄即经过GC的次数
- 目的:对于年轻和老年的对象,制定不同的GC策略,降低整体内存管理的开销
- 不同年龄的对象处于heap的不同区域
年轻代
- 常规对象分配
- 由于存活对象很少,可以采用copying collection
- GC吞吐率很高
老年代
- 对象趋于一直活着,反复复制开销较大
- 可以采用 mark-sweep collection
G内存管理及优化
Go内存分配
Go会提前将内存分块,在需要分配时返回一块大小最接近的一块
- 调用系统调用mmap(),向OS申请一大块内存,例如4MB
- 先将内存划分成大块,例如8KB,称作mspan
- 再将大块继续划分成特定的小块,用于对象分配
- noscan mspan:分配不包含指针的对象----GC不需要扫描
- scan mspan:包含只针对对象----GC需要扫描
- 由于是很高操作,所以会比较占用cpu
缓存
TCMalloc:thead caching
- Go语言采用GMP模式
- 每个p包含一个mcache用于快速分配,用于为绑定于p上的g分配对象
- mcache 管理一组mspan
- 当mcache中的mspan分配完毕,向mcentral申请带有未分配块的mspan
- 当mspan中没有分配的对象时,mspan会被缓存在mcentral中,而不是立刻释放并归还给OS
内存分配路径
g -> m -> p -> mcache ->mspan ->memory block -> return pointer
优化方案
Balanced GC
- 每个g都绑定一大块内存(1KB),称作goroutine allocation buffer
- GAB用于noscan类型的小对象分配: < 128B
- 使用三个指针维护GAB
- Bump Pointer(指针碰撞)风格对象分配
收益:高峰期能降低CPU使用率4.6%,核心接口时延下降4.5%~7.7%
编译器和静态分析
编译器结构
静态分析
- 不执行程序代码,推导程序的行为
- 控制流:推导程序执行的流程
- 数据流:数据在控制流上的传递
- 通过今天静态分析我们可以知道更多关于程序的内容
- 过程内分析:仅在函数内部进行分析
- 过程间分析:考虑过程调用参数传递和返回值的数据流和控制流
Go编译器优化
函数内联
就是把将被调用的函数体callee的副本替换到调用的位置上,同时重写代码以反映参数的绑定
优点:
- 消除函数调用开销
- 将过程间分析转为过程内分析,帮助其他优化
缺点:
- 函数体变大
- 编译生成的Go镜像变大
逃逸分析
分析代码中指针的动态作用域:指针在何处可以被访问 大致思路:
- 从对象分配处出发,沿着控制流,观察对象的数据流
- 若发现指针p在当前作用域s:
- 作为参数传递给其他函数
- 传递给全局变量
- 传递给其他goroutine
- 传递给已经逃逸的指针指向对象