Go 内存管理 & 编译器优化思路 | 青训营笔记

88 阅读2分钟

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

内存管理

这节课主要介绍了Go 内存分配和编译器相关知识展开,以及一些目前 Go 内存管理过程中问题,并给出解决方案,视频中还通过对编译器基本算法讲解,引出了编译器优化路径。

Go内存分配

分块

分块的目标是为对象在heap上分配内存。
首先提前将内存分块,调用系统调用mmap()向操作系统申请一大块内存(如4MB),先将内存划分成大块(如8KB)为mspan,再继续将大块继续划分为特定大小的小块,用于对象分配。其中,noscan mspan 是指分配不包含指针的对象,GC不需要扫描; scan mspan 是分配包含指针的对象,GC 需要扫描。
对象分配要根据对象的大小,选择最合适的块返回。

缓存

image.png
对于上图,可以进行如下解释:

TCMalloc:thread caching
每个p包含一个mcache用于快速分配,用于为绑定于p上的g分配对象
mcache 管理一组 mspan
当mcache 中的 mspan 分配完毕,向 mcentral 申请带有未分配块的 mspan
当 mspan 中没有分配的对象,mspan 会被缓存在 mcentral 中,而不是立刻释放并归还给操作系统。

Go内存管理优化

因为涉及对象分配时,对象分配是非常高频的操作,每秒分配GB级别的内存,并且其中小对象的占比较高,Go内存分配比较耗时,分配路径描述为:g->m->p->mcache->mspan->memory block-> return pointer,pprof是对象分配函数最频繁调用的一类。

内存优化方案

Balanced GC

每个 g 都绑定一大块内存 (1 KB) ,称作 goroutine allocation buffer (GAB),而 GAB 用于 noscan 类型的小对象分配小于128 B,并且使用三个指针维护GAB: base, end, top。采用 Bump pointer (指针碰撞) 风格进行分配,特点是对象分配无须和其他分配请求互斥分配、动作简单高效。