这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
Go 内存管理及优化
Go 内存分配思想 - 分块
内存分块流程
- 系统调用 mmap( ) 向 OS 申请一块足够大的内存
- 将内存划分成大块,即
mspan - 将大块划分成特定大小的小块,用于对象分配
- noscan mspan
GC 不需要扫描- 原理:因为分配的是不包含指针的对象
- scan mspan
GC 需要扫描- 原理:因为分配的是包含指针的对象
Go 内存分配思想 - 缓存
线程缓存的 Malloc
G(goroutine) M(thread) P(processor) 模型
TCMalloc 原理
- 每一个 P 包含一个 mcache 用于快速分配
- mcache 是为绑定在 P 上的 G 分配对象
- mcache 作为缓存区 管理一组 mspan
- 当 mspan 中没有分配对象时,mspan 会被缓存在 mcentral 全局中心缓存中,并不会立即释放归还给 OS
Go 内存分配 - 优化策略
Go 实际开发中的背景
-
对象分配非常高频率
- 每秒分配 GB 级别的内存
- 对象分配的函数是最频繁调用的函数之一
-
小对象占比较高
-
分配路径流程
- G -> M -> P -> mcache -> mspan -> memory block -> return pointer
- 路径过长
Balanced GC 优化策略
-
G(goroutine) A(allocation) B(buffer)
- GAB 相当于是是一个大对象
- 本质是将多次的小对象的分配合并成一次的大对象分配
-
每个 G 绑定一大块内存
-
GAB 用于 noscan 类型的
小对象(<128 B)分配 -
三指针维护 G A B
- base
- end
- top
-
内存分配流程
- 直接操作 top 指针移动指定长度,并返回,完成内存分配
if top+size <= end {
addr := top
top += size // move pointer
return addr // memory
}
-
Balanced GC 性能缺陷
- GAB 中 存活的小对象会使 GAB 整个被标记为存活的
- 导致 GAB 整个被占用,造成内存延迟释放
-
Balanced GC 性能优化方案
- 当多个 GAB 中存在部分存活对象,且所占内存总和超过了一定阈值,我们将这些 GAB 中的内存复制到另外分配的 GAB 中
- 这样子原先的 GAB 就能释放掉
Go 编译器优化
Go 编译器现状
采用的优化少为了缩短编译时间,Go没有进行较为复杂的代码分析和优化
编译器优化原因
编译器优化能够使用户无感知,重新编译即可获得性能收益编译器优化属于通用性优化
编译器优化方向
函数内联
原理:
- 直接将被调用的函数体的副本替换到调用位置上
优势:
- 消除函数调用开销,因为不需要传递参数等
- 将过程间分析转化为过程内分析,能够为逃逸分析优化提供便利
- 函数 inline 后性能提升明显
缺点:
- 函数体变大
- 编译生成的 Go 镜像变大
逃逸分析
原理:
- 指针在哪里可以被访问
逃逸表现:
- 指针作为参数传递给其他函数
- 指针传递给全局变量
- 指针传递给其他的 goroutine
- 指针传递给已经逃逸的指针 所指向的对象