内存管理及编译器优化 | 青训营笔记

内存管理及编译器优化 | 青训营笔记

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

一、 自动内存管理

由程序语言的运行时系统管理动态内存,保证内存使用的正确性和安全性,避免手动内存管理,专注于实现业务逻辑,

1. 相关概念

  • Mutator:业务线程,分配新对象,修改对象指向关系

  • CoIlector:GC 线程,找到存活对象,回收死亡对的内存空间

  • SeriaI GC:只有一个 collector

  • ParaIIeI GC:支持多个 collectors 同时回收的 GC 算法

  • Concurrent GC:mutator(s) 和 collector(s) 可以同时进行

    image.png

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 内存管理来说是一个大对象,本质上是将多个小对象的分配合并成一次大对象的分配。

image.png

但 GAB 的对象分配方式会导致内存被延迟释放,可以用 copying GC 的算法管理小对象,根据对象的生命周期,使用不同的标记和清理策略。

三、编译器和静态分析

1. 编译器的结构

编译器是一种重要的系统软件,能够识别符合语法和非法的程序,生成正确且高效的代码。

image.png

编译器整个结构分为前端和后端,前端是分析部分,词法分析生成词素 (lexeme),语法分析生成语法树,语义分析收集类型信进行语义检查,中间表示生成,生成 intermediate representation (IR)。后端是综合部分,先进行机器无关的代码优化,生成优化后的 IR,然后生成目标代码。

2. 静态分析

不执行程序代码,推导程序的行为,分析程序的性质。通过分析控制流和数据流,我们可以知道更多关于程序的性质,根据这些性质优化代码。

3. 过程内分析和过程间分析

过程内分析仅在函数内部进行分析,过程间分析考虑程序调用时参数传递和返回值的数据流和控制流。过程间分析需要同时分析控制流和数据流,相对复杂。

四、GO 编译器优化

编译器优化将获性能受益,所以编译器优化很有必要。

1. 函数内联

函数内联将被调用函数的函数体 (callee) 的副本替换到调用位置 (caller) 上,同时重写代码以反映参数的绑定,将消除函数调用开销,例如传递参数、保存寄存器等,还能将过程间分析转化为过程内分忻,帮助其他优化,例如逃逸分析。函数被内联后将获得性能的提升。

但函数内联会使函数体变大,instruction cache (icache) 不友好,也会使编译生 Go 镜像变大。不过函数内联在大多数情况下是正向优化的。

2. Beast Mode

Beast Mode 是一种调整函数内联的策略 , 使更多函数被内联,可以降低函数调用的开销,增加了其他优化的机会,比如能够进行逃逸分析。但会使 Go 镜像和编译时间增加。