Go 语言内存管理详解 | 青训营笔记

68 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 内存管理过程中问题和解决方案,还有编译器的优化路径。知识点很多,然后我觉得编译器静态分析和优化是重点难点,还有许多不太懂的地方。