内存管理及编译器优化 | 青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
一、 自动内存管理
由程序语言的运行时系统管理动态内存,保证内存使用的正确性和安全性,避免手动内存管理,专注于实现业务逻辑,
1. 相关概念
-
Mutator:业务线程,分配新对象,修改对象指向关系
-
CoIlector:GC 线程,找到存活对象,回收死亡对的内存空间
-
SeriaI GC:只有一个 collector
-
ParaIIeI GC:支持多个 collectors 同时回收的 GC 算法
-
Concurrent GC:mutator(s) 和 collector(s) 可以同时进行
2. 回收方式
-
Tracing garbage collection:追踪垃圾回收
清理所有不可达对象,步骤如下:
- Copying GC:复制对象 GC
- Mark-sweep GC:标记-清理 GC
- Mark-compact GC:标记-压缩 GC
-
Generational GC:分代 GC
针对年轻和老年的对象,制定不同的 GC 策略,使不同年龄的对象处于 heap 的不同区域,降低整体内存管理的开销。
-
Reference counting:引用计数
每个对象都有一个与之关联的引用数目,当且仅当引用数大于 0 时使目标存活,等于 0 则销毁。
二、Go 内存管理及优化
1. 内存分配
- 分块:提前将 heap 内存分块,根据对象的大小,选择最合适的块返回
- 缓存:由三级分配过程给对象分配内存,没有分配对象的内存块将被放入缓存而不是进行回收
2. 内存管理优化
pprof
3. Balanced GC
每个 g 都绑定一大块内存 (1 KB),称作 goroutine allocation buffer (GAB),GAB 用于 noscan 类型的小对象分配:< 128 B,使用三个指针维护 GAB:base,end,top。GAB 对于 Go 内存管理来说是一个大对象,本质上是将多个小对象的分配合并成一次大对象的分配。
但 GAB 的对象分配方式会导致内存被延迟释放,可以用 copying GC 的算法管理小对象,根据对象的生命周期,使用不同的标记和清理策略。
三、编译器和静态分析
1. 编译器的结构
编译器是一种重要的系统软件,能够识别符合语法和非法的程序,生成正确且高效的代码。
编译器整个结构分为前端和后端,前端是分析部分,词法分析生成词素 (lexeme),语法分析生成语法树,语义分析收集类型信进行语义检查,中间表示生成,生成 intermediate representation (IR)。后端是综合部分,先进行机器无关的代码优化,生成优化后的 IR,然后生成目标代码。
2. 静态分析
不执行程序代码,推导程序的行为,分析程序的性质。通过分析控制流和数据流,我们可以知道更多关于程序的性质,根据这些性质优化代码。
3. 过程内分析和过程间分析
过程内分析仅在函数内部进行分析,过程间分析考虑程序调用时参数传递和返回值的数据流和控制流。过程间分析需要同时分析控制流和数据流,相对复杂。
四、GO 编译器优化
编译器优化将获性能受益,所以编译器优化很有必要。
1. 函数内联
函数内联将被调用函数的函数体 (callee) 的副本替换到调用位置 (caller) 上,同时重写代码以反映参数的绑定,将消除函数调用开销,例如传递参数、保存寄存器等,还能将过程间分析转化为过程内分忻,帮助其他优化,例如逃逸分析。函数被内联后将获得性能的提升。
但函数内联会使函数体变大,instruction cache (icache) 不友好,也会使编译生 Go 镜像变大。不过函数内联在大多数情况下是正向优化的。
2. Beast Mode
Beast Mode 是一种调整函数内联的策略 , 使更多函数被内联,可以降低函数调用的开销,增加了其他优化的机会,比如能够进行逃逸分析。但会使 Go 镜像和编译时间增加。