高性能 Go 语言优化与落地实践-内存与编译器优化| 青训营笔记

196 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记.

0x0 Pre

在本文章中会了解到:

  1. 垃圾回收(重要)
  2. Go 垃圾回收(了解)
  3. Go 编译器的知识(了解)

很多内容和 Java GC 很类似。

0x1 自动内存管理(GC)

自动内存管理也叫垃圾回收,由程序语言运行时系统管理动态内存。

  • 避免手动内存管理,专注于实现业务逻辑
  • 保存内存使用的正确性和安全性:比如 double-free 和 use after free 的问题 相关概念:
  • Mutator: 业务线程,分配新对象,修改对象的指向关系
  • Collector:GC线程,找到存活对象,回收死亡对象
  • Serial GC:只有一个 collector
  • Parallel GC:支持多个 collectors 同时回收的 GC 算法
  • Concurrent GC:mutators 和 collectors 可以同时执行

image.png

GC 算法的指标:

  • 安全性:不能回收存活的对象
  • 吞吐量:花在GC上的时间
  • 暂停时间:业务是否能感知内存回收,STW
  • 内存开销

常见垃圾回收算法:追踪垃圾回收(Tracing garbage collection)和引用计数法(Reference Counting)

追踪垃圾回收

image.png 追踪垃圾回收算法大致分为三步:

  1. 标记根对象:静态常量,全局变量,常量,线程栈等
  2. 标记可达对象:从根对象触发,找到所有可达对象
  3. 清理不可达对象:清理不可达对象有着不同的策略(和 JVM 垃圾回收类似),具体回收策略要根据对象的声明周期来确定
    • Copying GC:将存活对象复制到另外的内存空间,类似 JVM 中的幸存区 From 和 to。
    • Mark-Sweep GC:即标记清楚法,将死亡对象的内存标记为“可分配”
    • Mark-Compact GC:移动并且整理存活对象,原地整理对象

这里的具体回收策略使用了分代 GC:

image.png

年轻代中的对象存活时间较短,可以采用 copying GC,老年代中存活时间较长,可以采用Mark-Sweep GC。

引用计数法

image.png

每个对象都有一个与之关联的引用数目,当引用数目大于0的时候对象存活。 优点:

  • 内存管理的操作被平摊到程序执行过程中
  • 内存管理不需要了解 runtime 的实现细节(比如 C++ 中的智能指针) 缺点:
  • 维护引用计数的开销较大:需要通过原子操作保证对引用计数操作的原子性和可见性
  • 无法回收环形数据结构————weak reference
  • 内存开销:每个对象都引入额外的内存空间存储引用数目

0x2 Go内存管理及优化(Go GC)

Go 内存分配

  1. 分块 目标:为对象在堆上分配内存。

    image.png 提前将内存分块:

    • 首先系统调用 mmap() 向 OS 申请分配一块内存空间,比如4MB
    • 将获得的内存划分为大块,例如 8KB,称为mspan, noscan mspan 用于分配不包含指针的对象,scan mspan 分配包含指针的对象。GC 只会扫描scan mspan
    • 再将大块继续划分为特定大小的小块,用作对象分配
  2. 缓存

image.png

Go 内存缓存借鉴了 TCMalloc(Thread Caching) 的技术加快整体内存分配速度。

字节跳动的针对小对象分配的优化方案:Balanced GC,并且取得了不错的效果  。

0x3 编译器和静态分析

image.png

静态分析分为数据流和控制流分析,也分为过程间分析和过程中分析。

image.png

0x4 Go 编译器优化

image.png

函数内联