高性能Go语言发行版优化与落地实践 | 青训营笔记
这是我参与「第五届青训营 」笔记创作活动的第4天
话题:性能优化
-
目的:提升系统的处理能力,为用户带来体验的提升,实现对资源的高效利用。
-
方向:业务层优化、语言运行时优化(本门课的方向)
目录
正文
自动内存管理
动态内存:程序在运行时根据需求动态分配的内存。而对于动态分配的内存,难以做到手动的内存管理,因为难以做到内存使用的正确性和安全性。
为此,引入了动态内存管理的概念。

而对于一个GC算法,我们关心其安全性,吞吐量,暂停时间,内存开销
常见GC算法
-
追踪垃圾回收:从根变量开始,求所有可达对象闭包,标记为存活,对死亡对象进行回收
贯穿GC算法效率的核心:根据对象的生命周期,使用不同的标记和清理策略
-
Copying GC
-
Mark-sweep GC

-
Compact GC

-
-
分代GC(Generational GC)
一个事实是:很多对象在分配之后不长时间就不再使用了,而我们可以定义年龄(对象经历GC的次数),依据年龄划分对象组(不同年龄的对象处于heap的不同位置)不同的GC策略,从而降低整体的内存管理开销。

-
引用计数
每个对象都维护一个引用数目。当一个对象的的引用数目大于0时定义为存活。 优点:
- 内存管理的开销被平摊到程序执行过程中
- 内存管理也不需要了解runtime的实现细节
缺点:
- 维护引用计数开销大
- 无法回收环形数据结构
- 当引用层级复杂时,回收内存依旧可能发生STW
Go内存管理及优化
Go的内存管理
- Go在堆上为对象分配内存的方法:提前进行内存分块,并在使用时根据对象的大小选择合适的内存块并返回。
- Go内存分配的缓存机制
p包含一个mcache,mchache管理一组mspan,当mchache中使用完毕后,可直接向mcentral申请没有分配块的mspan。当mspan上的对象被释放后,不会直接还给OS,而是会继续由mcentral缓存。
Go的内存管理优化
Go内存管理的问题
-
内存分配是相当高频的操作
-
小对象的分配在总的分配占比非常高
-
Go的内存分配路径很长,导致内存分配耗时很高
优化方案:Balanced GC
- 在该方案下,GAB对于Go的内存管理而言就是一个大的对象,其目的就是减少多次小对象走长路径分配内存的次数。但是还是存在一个问题,就是当一个GAB只存在几个小对象时,对Go的内存管理而已,整个GAB大对象都是占用的,导致了内存的延迟释放。
- 解决方案:统计GAB总大小,设定一个阈值,当大小超过阈值后,使用Copying GC策略(小对象使用此策略开销很小)重新分配小对象到新的GAB中,释放原先的GAB。
编译器和静态分析
标准的编译器结构
静态分析
-
在编译阶段,由于无法执行代码和测试输入,可以在不执行代码的前提下进行静态分析优化编译。
-
其中有两个考量的方面:控制流和数据流

- 根据控制流和数据流,我们可以优化代码,比如说下图左侧的代码因为输出恒为4,我们编译优化后直接使用
return 4;代替原函数。
- 根据控制流和数据流,我们可以优化代码,比如说下图左侧的代码因为输出恒为4,我们编译优化后直接使用
-
由静态分析,我们得出两大类分析:过程内分析、过程间分析

- 说人话就是,过程内不考虑输入输出,对函数的内部进行分析。而过程间考虑了调用时的输入输出,过程更加复杂。
Go编译器优化
函数内联(Inlining)
-
说人话就是将调用的函数直接拷贝到调用处,使得两个函数间的调用转换为一个函数的调用。更重要的目的在于:将方便将过程间分析转化为过程内分析,减少静态分析的复杂性。
-
内联的缺点
-
编译生成的Go镜像将增大
-
过于复杂和冗长的函数体会降低CPUcache的命中率
-
优化方案:Beast Mode
- 方案的目的在于调整函数内联的策略,使得更多的函数被内联
- 逃逸分析
引用参考
高性能 Go 语言发行版优化与落地实践 .pptx - 飞书云文档 (feishu.cn)