这是我参与「第五届青训营 」伴学笔记创作活动的第5天。
一、本堂课重点内容:、
- Go内存分配
- Go内存管理优化
二、详细知识点介绍:
- Go内存分配
1.1 分块
- 目标:为对象在heap上分配内存
- 提前将内存分块
- 调用系统调用mmap()向os申请内存
- 将内存划分为大块mspan
- 大块继续划分为特定大小的小块
- 分配不包含指针的对象(noscan mspan)/包含指针的对象(scan mspan)
1.2 缓存
- 当mcache中的mspan分配完毕,向mcentral申请带有未分配块的mspan
- 当mspan中没有分配的对象,mspan会被缓存在mcentral中,而不是立即释放还给OS
- Go内存管理优化
- 对象分配是非常高频的操作
- 小对象占比较高
- Go内存分配比较耗时
- 分配路径长:g->m->p->mcache->mspan->memory block->return pointer
1.3 优化方案 Balance GC
- 每个g绑定一大块内存(1KB)称作 goroutine allocation buffer(GAB)
- GAB用于noscan类型的小对象分配
- 使用三个指针维护GAB:base,end,top
- Bump pointer风格对象分配
- 无须和其他分配请求互斥
- 分配动作简单高效(移动top指针)
- Balance GC
- 将多个小对象的分配合并成一次大对象(GAB)的分配
- 问题:内存会被延迟释放
- 方案:移动GAB中存活的对象
- GAB总大小超过一定阈值,将GAB中存活的对象复制到另外分配的GAB中
- 本质:用copying GC的算法管理小对象
- 编译器和静态分析
2.1 编译器的结构
- 分析部分
- 词法分析:生成词素
- 语法分析:语法树
- 语义分析:语义检查
- 中间代码生成:Intermediate representation(IR)
- 综合部分
- 代码优化:机器无关优化
- 代码生成:生成目标代码
2.2 静态分析
- 不执行代码,推导程序的行为,分析程序的性质
- 控制流(Control flow):程序执行的流程
- 数据流(Data flow):数据在控制流上的传递
- 根据性质优化代码
2.3 过程内分析和过程间分析
- 过程内分析:仅在函数内部进行分析
- 过程间分析:考虑函数调用时参数传递和返回值的数据流和控制流
- 数据流分析得知参数具体类型,才能知道调用哪个函数
- 根据参数具体类型产生新的控制流
- 同时分析数据流和控制流,联合求解
- Go编译器优化
- 编译优化的思路
- Tradeoff:用编译时间换取更高效的机器码
- Reast mode
- 函数内联
- 逃逸分析
- 默认栈大小调整
4.1 函数内联(inlining)
- 内联:将被调用函数的函数体的副本替换到调用位置上,同时重写代码以反映参数的绑定
- 优点:
- 消除了函数调用的开销
- 将过程间分析转化为过程内分析,帮助其他优化,例如逃逸分析
- 缺点
- 函数体变大
- 编译生成的Go镜像变大
- 根据调用和被调函数的规模调整内联策略
- 调整函数内联的策略,使更多的函数被内联
4.2 逃逸分析
- 分析代码中指针的动态作用域:指针在何处可以被访问
- 大致思路:
- 从对象分配处出发,沿着控制流,观察对象的数据流
- 指针p在当前作用域s
- 作为参数传递给其他函数
- 传递给全局变量
- 传递给其他gproutine
- 传递给已逃逸的指针指向的对象
- 则指针p指向的对象逃逸
- Beast mode:函数内联拓展了函数边界,更多对象不逃逸
- 优化:未逃逸的对象可以在栈上分配
- 对象在栈上的分配和回收很快(移动栈指针)
- 减少在heap上的分配,GC效率提升
三、课后个人总结:
本堂课程学习了Go内存管理和编译优化的相关知识,学习了Go内存分配和编译优化的基本思路。了解到了Balance GC优化对象分配以及Beast mode提升代码性能。其中分析问题的方法与解决问题的思路在其他语言优化中也同样适用。