这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记 主要记录了GO语言优化,编译器和静态分析,Go编译器优化
GO语言优化
自动内存管理
动态内存:程序在运行时根据需求时动态分配的内存
自动内存管理:由程序语言的运行时系统管理动态内存
- 避免手动内存管理,专注于实现业务逻辑
- 保证内存使用的正确性和安全性
三个任务
- 分配新空间
- 寻找存活对象
- 回收死对象
相关概念
- mutator:业务现场,分配新对象,修改对象指向关系
- Collector:GC线程,寻找存活对象,回收死亡对象
- Serial GC:只有一个collector
- Parallel GC:有多个collector,同时执行GC算法
- Concurrent GC:mutator和collector可以同时执行
如何判断对象死亡?
追踪垃圾回收(可达性分析)
标记根对象
标记可达对象
清理所有不可达对象(3个gc算法)
根据具体需求选择不同gc算法
gc依据:分代假说
引用计数法
每个对象都有与之关联的引用数目,当引用数目<=0时回收该对象。
内存优化
内存分配-分块
目标:将对象在heap上分配内存
提前将内存分块
- 调用系统调用mmap()想os申请一块大内存
- 将大内存分大块,称为mspan
- 再将大块分成小块,用于对象分配
- noscan mspan:分配不包含指针的对象
- scan mspan:分配包含指针的对象
对象分配,根据对象的大小,选择最合适的内存分块
内存分配-缓存
TCMalloc:thread caching
G:Goroutine
M:Processor,逻辑处理器
P:Machine,系统物理线程
每个p包含一个mcache用于快速分配,用于为绑定于p上的goroutine分配对象
mcache管理一组mspan
当mcache中的mspan分配完毕,向mcentral申请带有未分配的mspan
当mspan中没有分配的对象,mspan会被缓存在mcentral中,而不是立刻释放还给os
balanced gc
- 每个g都绑定一块大内存(GAB)
- GAB用于noscan类型的小对象分配
- 使用三个指针维护GAB:base,end,top
- Bump pointer风格对象分配
无需和其他分配请求互斥
分配动作简单高效
if top + size <=end{
addr:=top
top+=size
return addr
}
本质:将对个小对象分配合成一次大对象的分配
问题:GBA对象分配方式会导致内存被延迟释放
方案:移动GAB中存活的对象
本质:copying gc来管理小对象
基本思想:根据对象的生命周期来管理对象。
编译器和静态分析
编译器结构
重要的系统软件
- 识别符合语法和非法的程序
- 生成正确且高效的代码
分析部分(前端 front end)
- 词法分析,生成词素
- 语法分析,生成语法树
- 语义分析,收集类型信息,进行语义检查
- 中间代码生成,生成intermediate representatin
综合部分(后端 back end)
- 代码优化,机器无关优化,生成优化后的IR
- 代码生成,生成目标代码
静态分析
静态分析:不执行代码,推导程序执行
性质
- 控制流:程序执行流程
- 数据流:数据在控制流上传递
过程内和过程间分析
过程内:仅在函数内部分析
过程间:考虑过程吊桶式参数的传递和返回值的数据流和控制流
Go编译器优化
函数内联
内联:将被调用的函数的函数体的副本替换到调用位置上,同时重写代码以反映参数的绑定
优点:
- 消除函数调用开销,例如参数传递,返回值等等
- 将过程间转化为过程内分析,帮组其他优化。
缺点:
- 函数体变大,icache不友好
- 编译生成的go镜像变大
在大多数情况下,函数内联是正向的
Beast Mode
调整函数内敛策略,使更多函数被内联
- 降低函数调用开销
- 增加其他优化的机会
逃逸分析
分析指针的动态作用域:指针在何处可以被访问
当前p指针在s作用域:
- 作为参数传递给其他函数
- 传递给全局变量
- 传递给其他的goroutine
- 传递给已经逃逸的对象
则p逃逸出s,反之没有逃逸
beast mode拓展了函数边界,使更多对象不逃逸
优化: 未逃逸的对象可以在栈上分配
对象在栈上分配和回收很快:移动sp
减少heap上的分配,降低gc负担