Go 内存管理及优化 | 青训营笔记

103 阅读4分钟

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

Go 内存管理及优化

Go 内存分配 - 分块

在Golang中,其提前将内存分块,过程如下:

  • 调用系统调用 mmap() 向OS申请一大块内存,比如4MB
  • 先将内存划分为多个大块,例如8KB,被称为 mspan
  • 再将大块继续划分成特定大小的小块,用于对象分配。根据不同粒度,可以划分出不同大小的小块,8B,16B,24B...
  • noscan mspan:分配不包含指针的对象,此时GC不用扫描
  • scan mspan:分配包含指针的对象,此时GC需要扫描

然后根据对象的大小,选择最合适的块将其返回即可

Go 内存分配 - 缓存

Go语言的内存管理参考了TCMalloc,其中T表示Thread,C表示Caching

  • 首先根据Go语言的GMP调度模型(G为goroutine,M为Machine也叫thread内核线程,P为Processor处理器,P调度G到M上执行)
  • 每一个P都会拥有一个mcache用于快速分配内存(因此不用加锁),也就是给P上的G分配内存
  • 其中mcache管理了一组mspan
  • 当mspan内存分配完毕后会向mcentral(M共享,加锁访问)申请带有未分配块的mspan
  • 当mspan中的块都未被分配时(可能是对象被回收了),mspan会被缓存到mcentral中,而不是立刻释放并归还给OS。如果有需要分配的话就可以立即把这个mspan分配出去。根据一定策略还回给OS。

Go 内存管理优化

mallocgc 内存分配

  • 需要知道的一点是:对象分配是非常高频的操作,每秒分配GB级别的内存是很常见的。

  • 并且在对象分配中,小对象的占比相对较高,

  • 并且内存分配的流程比较耗时

    • 分配路径长:g -> m -> p -> mcache -> mspan -> memory block ->return pointer
    • 查看 pprof\text{pprof} ,对象分配的函数是最频繁调用的函数之一

我们的优化方案:Balanced GC

  • 将每个 g 都绑定一大块内存(1KB),称为goroutine allocation buffer(GAB)

  • GAB用于noscan类型的小对象分配:< 128B

  • 使用三个指针维护GAB:base,end,top

  • Bump pointer(指针碰撞)风格对象分配

    • 无须和其他分配请求互斥
    • 分配动作简单高效

GAB对于Go内存管理来说是一个对象。

其本质就是将多个小对象的分配合并成一次大对象的分配

不过也有缺点,就是GAB的对象分配方式会导致内存被延迟释放(GAB中的一块小内存被释放了,并不会将GAB释放),解决办法就是移动GAB中的存活对象,当GAB总大小超过一定的阈值时,将GAB中存活的对象复制到另外分配的GAB中,这样原先的GAB就可以释放了,从而避免内存泄漏,从本质上看,也就是使用Copying GC来管理小对象

编译器和静态分析

编译器的结构

编译器是重要的系统软件,它的作用主要有两个:

  • 识别符合语法和非法的程序
  • 生成正确且高效的代码

对于整个编译器结构来说,其一般被分为分析部分(前端)综合部分(后端)

分析部分(前端)

分析部分主要的工作如下:

  • 词法分析,生成词素(lexeme)
  • 语法分析,生成语法树
  • 语义分析,收集类型信息,进行语义检查
  • 中间代码生成,生成intermediate representation(IR)

综合部分(后端)

综合部分的主要工作如下:

  • 代码优化,机器无关优化,生成优化之后的 IR
  • 代码生成,生成目标代码

而我们要做的就是编译器的后端优化

静态分析

静态分析指的是不执行程序代码,推到程序的行为,从而分析程序的性质。

其中有两个相关的概念:

  • 控制流(Control Flow):程序执行的流程
  • 数据流(Data Flow):数据在控制流上的传递

通过分析控制流和数据流,我们可以知道更多关于程序的性质,从而根据这些性质来优化代码。

过程哪分析和过程间分析

过程内分析(Intra-procedural analysis) 指的是仅在函数内部进行分析

过程间分析(Inter-procedural analysis) 则要考虑过程调用时参数传递和返回值的数据流和控制流。

i的具体类型未知时,需要根据数据流分析i的具体类型,而分析具体类型时,又产生了新的控制流因为AB都实现了接口I里的foo()函数,因此过程间分析需要同时分析控制流和数据流,也就是联合求解,是比较复杂的。

而根据控制流分析,可以知道其调用的是Afoo()方法。