这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天。
主要是学习了Go语言发行版优化与落地实践,理解了Go语言内存管理和编译器后端的代码优化。
Go语言优化
- 内存管理
- 编译器
一、介绍
-
为什么:提升系统处理能力、减少不必要消耗,改善用户体验、高效利用资源
-
两个层面:
业务代码 - SDK - 基础库 - 语言运行时 - OS
a. 业务层
- 针对特定场景
- 易获得较大性能收益
b. 语言运行时
- 解决通用性能问题
- Tradeoffs
c. 数据驱动
- 自动化性能分析工具 pprof
- 依靠数据而非猜测
- 首先优化最大瓶颈
Go SDK
接口 - Commands - APIs - New APIs
实现 - Complier - Scheduler - GC - Runtime - Libs - Profiling
- 性能优化的可维护性:
保证接口稳定的前提下改进具体实现
测试用例:尽可能多的场景 - 测试驱动开发
文档:做了什么,没做什么,能达到怎样的效果
隔离:选项控制是否开启优化
可观测:必要日志输出
二、自动内存管理(垃圾回收)
动态内存:根据需求分配的内存
由程序语言运行时系统管理动态内存,既可以专注于实现业务逻辑,又可以保证内存的正确性和安全性。
三个任务:为新对象分配空间,找到存活对象,回收死亡对象的内存空间
Mutator threads 业务线程:分配新对象,修改对象指向关系;
Collector GC线程:找到存活对象,回收死亡对象的内存空间;
Serial GC:只有一个collector;
Parallel GC:支持多个collectors同时回收GC;
Concurrent GC:mutators和collector可以同时执行;Collectors必须感知对象指向关系变化。
评价:
- 安全性:不能回收存活的对象;
- 吞吐率:1-GC时间/程序执行时间;
- 暂退时间:业务是否感知;
- 内存开销:GC元数据开销;
技术:
- 追踪垃圾回收 - 指针对象不可达对象 - 标记&清理(Copying GC、Mark-sweep GC、Mark-compact GC)
根据对象生命周期,使用不同标记和清理策略
分代GC - 一种内存管理方式
分代假说:most objects die young 对象:经历GC的册数 - young(copying 吞吐率高) & old generation(mark-sleep)
- 引用计数 - 对象存活条件:大于0
优点:内存管理平摊到程序执行过程中,不需要了解runtime实现细节;
缺点:维护开销较大(原子操作),无法回收环形数据结构,内存开销,回收内存时可能引发暂停。
三、内存分配
分块:为对象在heap上分配内存 - 根据对象大小、选择最合适的块返回;
缓存:TCMalloc - thread caching
非常高频、每秒GB、小对象占比较高、Go内存分配路径过程耗时。
Balanced GC
GAB(goroutine allocation buffer)
用于noscan类型的小对象(<128B)
三个维护指针:base,end,top
将多个小对象的分配合并成一次大对象的分配(copying GC)
编译器
源代码 - 词法分析器 - 语法分析器 - 语义分析器 - 中间表示 - 代码优化 - 代码生成 - 目标代码
静态分析:不执行程序代码,推导程序行为 - 控制流 & 数据流