这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记
引言
追求极致性能
-
性能优化是什么?
- 提升软件系统处理能力,减少不必要的消耗,充分发掘计算机算力
-
为什么要做性能优化
- 用户体验:带来用户体验的提升 ——让刷抖音更丝滑,让双十一购物不再卡顿
- 资源高效利用:降低成本,提高效率——很小的优化乘以海量机器会是显著的性能提升和成本节约
自动内存管理(垃圾回收)
由程序语言的运行时系统回收动态内存
追踪垃圾回收
-
回收条件:指针指向关系不可达的对象
-
标记:找到可达对象
- 求指针指向关系的传递闭包;从根对象出发,找到所有可达对象--标记所有通过指针能找到的对象
-
清理:所有不可达对象
- Copying GC —— 将存活对象复制到另外的内存空间
- Mark-sweep GC ——将死亡对象的内存标记为“可分配”
- Mark-compact GC ——移动并整理存活对象(相当于压缩)
- 根据对象的生命周期,使用不同的标记和清理策略
分代GC
争对年轻的老年的对象,制定不同的GC策略,降低整体内存管理的开销
-
年轻代
-
存活对象很少,可以采用copying collection
-
GC吞吐率很高
-
-
老年代
- 对象趋向于一直活着,反复复制开销较大
- 可以采用mark-sweep collection
引用计数
-
每个对象都有一个与之关联的引用数目 即记录它的入度
-
优点
- 内存管理的操作被平摊到程序执行过程中
- 内存管理不需要了解runtime的实现细节
-
缺点
-
维护引用计数开销大:通过原子操作保证对引用计数操作的原子性和可见性
-
无法回收环形数据结构 ——weak reference
-
内存开销:每个对象都引入的额外内存空间存储引用数目
-
回收内存依然可能引发暂停(链路太长)
-
Go内存管理及优化
Go内存分配
分块
- 目标:为对象在heap上分配内存
- 提前将内存分块
- 根据对象的大小,分配块
缓存
Go内存管理优化
分析
- 对象分配是非常高频的操作
- 小对象占比比较高
- Go内存分配比较耗时
优化方案 Balanced GC
- 每个g都绑定一大块内存(1kB)称为GAB
- GAB用于noscan类型的小对象分配: < 128 B
-
使用三个指针维护GAB base end top
-
Bump pointer(指针碰撞)分格对象分配
-
无须和其他分配请求互斥
-
分配动作简单高效
-
形如队列的方式插入,添加一个标记位区分不同对象
-
GAB对于Go内存管理来说是一个对象,会导致内存被延迟释放——其中一个对象没有释放,整个GAB都不呢个释放
-
方案:移动GAB存活的对象
- 设定阈值,用copying GC的算法管理小对象
编译器和静态分析
编译器的结构
静态分析
过程内分析和过程间分析
-
过程内分析
- 仅在函数内部进行分析
-
过程间分析
- 考虑过程调用参数传递和返回值的数据流和控制流
-
过程间分析联合求解,比较复杂
Go编译器优化
-
why?
- 用户无感知,重新编译获得性能收益
-
now
- 采用的优化少
- 编译时间较短,没有进行较复杂的代码分析和优化
-
编译优化的思路
- 场景:面向后端长期执行任务
- Tradeoff:用编译时间换取更高效的机器码
-
Beast mode
- 函数内联
- 逃逸分析
- 默认栈大小调整
- 边界检查消除
- 循环展开
函数内联
Beast Mode
- 调整函数内联的策略,使更多的函数被内联,更多对象不逃逸
- 未逃逸的对象可以在栈上分配
- 对象在栈上分配和回收很快,移动sp
- 减少在heap上的分配,降低GC负担