这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记
性能优化
-
是什么: 提高程序效率,减少不必要消耗
-
为什么:用户体验,资源高效利用
-
怎么做:
- 业务层优化:针对特定场景,具体问题,具体分析
- 语言运行时优化:解决更通用的性能问题
- 数据驱动:自动化性能分析工具 —— pprof
- 保证稳定的前提下进行优化,通过测试驱动,用完善的文档告诉用户优化的效果
自动内存管理
-
自动内存管理
目标:动态内存分配,查找存活对象,回收
Mutator:业务线程,分配对象,修改对象指向关系
Collector:GC线程,找到存活对象,回收死亡对象
Serial GC:单个 collector,会有 STW
Parallel GC:多个 collector,会有 STW
Concurrent GC:可以同时指向两种线程,无STW
追踪垃圾回收:
- 可达性分析,回收不可达的对象
过程:
1、首先标记根对象(常量,栈内临时变量,静态变量)
2、标记:通过对象之间的关系层层搜寻出可以达到的对象
3、清理:将不可达的对象清理
- 引用计数法
第二种对象回收标记法是引用计数法,对于每个对象都有一个关联的值记录被引用的数目,当引用为0,就是可回收对象。
对于两种方法的比较:
-
追踪垃圾回收
- 通过可达性分析,标记出存活对象,清理不可达对象
- 优点是不需要维护额外数据
- 缺点是会有 STW,对性能影响比较大。
-
引用计数法
- 每个对象有一个引用计数,当引用计数为0将会被清理
- 优点是内存管理被平摊到程序执行中
- 缺点是难以解决循环引用,维护引用计数开销较大
清理方式有3种:
1、标记复制:将存活对象复制到另一块空间,原空间就变为可使用空间
2、标记整理:将存活对象复制到空间的同一端,这样剩余空间就变为可用空间,是对标记复制的优化
3、标记清除:遍历空间,将死亡对象空间标为可分配,用 free list 管理。
- generational GC | 分代 GC
GC 年龄:就是一个对象经历过的GC次数
年轻代:使用标记整理
老年代:使用标记清除
Go 内存管理及优化
- 首先通过系统调用 mmap 申请内存
- 将内存都划分为大小为 8K 的mspan
- 每个 mspan 内划分等大小的小块,不同 mspan 小块大小可以不同,用于分配给对象内存
- mspan 分为 scan 和 noscan,其中 scan mspan 具有指针对象,会被 GC 扫描
- 通过 mcache 分配内存
- mcache 中每种分块大小的 mspan 都有一组
- 如果 mcache 中的 msapn 满了就会换新一块进来
-
Go 编译器优化
用户无感知,直接获得收益,通用性
- 思路
对于长期使用的代码,通过完善的编译优化提高执行速度
-
函数内联
- 消除函数调用开销(传参,寄存器)
- 过程间分析转为过程内分析
- 函数体变大,对instruction cache不好
- 对编译生成的 Go 镜像变大
-
逃逸分析:逃逸分析就是分析代码中指针的动态作用域,也就是指针可以在何处被访问
- 对于未逃逸的对象可以在栈上分配,对象在栈上分配和回收非常快
- 减少在 heap 上的分配,降低 GC 负担