这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
本节课介绍关于高性能 Go 语言发行版优化的内存管理优化,分享自动内存管理与 Go 内存管理知识,提供可行性的优化建议,围绕 Go 内存分配和编译器相关知识展开,探讨目前 Go 内存管理过程中问题,提出解决方案,同时将通过对编译器基本算法讲解,引出编译器优化路径。
本堂课的知识点:
性能优化及自动内存管理
提升处理能力,减少消耗,发掘算力
用户体验,资源高效利用
性能优化层面(从高到低):
业务代码
SDK
基础库
语言运行时
OS
可优化方向:业务层优化、语言运行时优化、数据驱动(pprof分析)
优化要求:性能优化与软件质量(Go SDK);在保证接口稳定的前提下改进具体实现;测试用例全面、有文档、可隔离(优化开关)、有日志
01. 自动内存管理
1.1 自动内存管理
动态分配内存:malloc()
垃圾回收:保证正确性、安全性
GC三个任务:为新对象分配空间;找到存活对象;回收死亡对象空间
Mutator:业务线程
Collector:GC线程
Serial GC;Parallel GC;Concurrent GC
Concurrent GC:必须感知对象指向关系的改变
评价GC算法:安全性、吞吐率、暂停时间、内存开销
1.2 追踪垃圾回收
回收条件:指针指向关系不可达的对象
回收流程:标记根对象(变量、常量、线程栈等)、标记存活对象、回收死亡对象(Copying GC->Mark-sweep GC->Mark-compact GC)
根据对象的生命周期,使用不同的标记和清理策略
1.3 分代GC
每个对象都有年龄:经过GC的次数
对年轻(Young generation)和年老(Old generation)的对象,指定不同GC策略,降低整体内存管理的开销
1.4 引用计数
每个对象都有一个与之关联的引用数目
存活条件:当且仅当引用数大于0
优点:内存管理的操作被平摊到程序执行过程中;内存管理不需要了解runtime的实现细节
缺点:维护开销大;无法回收环形数据结构;内存开销;回收内存时引发暂停
02. Go 内存管理和优化
2.1 Go内存分配
2.1.1 提前将内存分块
mmap()向OS申请一大块内存->内存划分成大块->划分成特定大小的小块->noscan mspan->scan mspan
2.1.2 缓存
2.2 Go内存管理优化
高频操作、小对象占比较高、分配耗时
2.3 字节优化方案:Balanced GC
每个g都绑定一大块内存(1KB),称作goroutine allocation buffer(GAB)
GAB:多个小对象的分配合并成一次大对象的分配
方案:移动GAB中存活的对象;当GAB总大小超过一定阈值时,将GAB中存活的对象复制到另外分配的GAB中;原先的GAB释放,防止内存泄漏
03. 编译器和静态分析
3.1 编译器的结构
3.2 静态分析(重点)
不执行程序代码,推导程序的行为,分析程序的性质
控制流:程序执行的流程
数据流:数据在控制流上的传递
根据性质优化代码
3.3 过程内分析和过程间分析
过程间分析:考虑过程调用时参数传递和返回值的数据流和控制流
04. Go 编译器优化(重点)
4.1 函数内联(lnlining)
将被调用的函数体的副本替换到调用位置上,同时重写代码以反应参数的绑定
优点:消除函数调用开销;将过程间分析转化为过程内分析
缺点:函数体变大;编译生成的Go镜像变大
4.2 Beast Mode
Go 函数内联受到的限制较多
Beast Mode:调整函数内联的策略,使更多函数被内联
开销:Go镜像增加;编译时间增加
4.2.1 逃逸分析
分析代码中指针的动态作用域:指针在哪可以被访问
思路:从对象分配处出发,沿着控制流,观察对象的数据流->若发现指针 p 在当前作用域 s ->则指针 p 指向的对象逃逸出 s,反之则没有逃逸出 s
Beast Mode:拓展函数边界,减少逃逸
优化:未分配的对象可以在栈上优化
4.2.2 性能收益
课后个人总结:
本节课学习了关于 Go 语言的内存管理优化、Go 内存分配和编译器相关知识、以及目前 Go 内存管理过程中问题和解决方案,还有编译器的优化路径。知识点很多,然后我觉得编译器静态分析和优化是重点难点,还有许多不太懂的地方。