性能优化及自动内存管理 | 青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
本节内容
Go 语言优化:
- 内存管理优化
- 编译器优化
性能优化
- 性能优化是什么?
- 提升软件系统处理能力,减少不必要的消耗,充分发掘计算机算力
- 为什么要做性能优化?
- 用户体验:带来用户体验的提升
- 资源高效利用:降低成本,提高效率 很小的优化乘以海量机器会是显著的性能提升和成本节约
- 性能优化的层面
- 性能优化与软件质量 我们进行开发的都是Go SDK,需要保证接口的稳定性,也就是版本和版本之间,需要的参数等基本是不需要改变的。
自动内存管理
- 动态内存:程序在运行时根据需求动态分配的内存:例如 malloc()
- 自动内存管理(垃圾回收) : 由程序语言的运行时系统管理动态内存
- 避免手动内存管理,专注于实现业务逻辑
- 保证内存使用的正确性和安全性,出现的问题例如:double-free problem use-after-free problem
- 三个任务
- 为新对象分配空间
- 找到存活对象
- 回收死亡对象的内存空间
相关概念
- Mutator : 业务线程,分配新对象,修改对象指向关系
- Collector : GC线程,找到存活对象,回收死亡对象的内存空间
- Serial GC : 只有一个collector
- Parallel GC :支持多个collectors同时回收GC算法
- Concurrent GC :mutator 和 collector 可以同时执行
- Collectors必须感知对象指向关系的改变(参考:8.2 写屏障技术 | Go 语言原本 (golang.design))
- GC算法的评价
- 安全性:不能回收存活对象 (基本要求)
- 吞吐率
- 暂停时间
- 内存开销:GC元数据开销
- GC算法
- 追踪垃圾回收
- 引用计数
追踪垃圾回收
- 对象被回收的条件:指针指向关系不可达的对象
- 标记根对象
- 静态变量、全局变量、常量、线程栈等(一定是存活的)
- 标记:找到可达对象
- 清理:所有不可达对象,也就是我们无法通过
清理策略 - Copying GC
*将对象复制到另外的内存空间
清理策略 - Mark-sweep GC
*使用 free list 管理空闲内存
就是将这些被标记的元素利用free list 链接起来,然后再进行新的内存分配的时候从这个链当中进行分配就可以了。
清理策略 - Compact GC
*原地整理对象
将存活的对象拷贝到内存开始的地方进行压缩,之后从后面开始分配就可以了
*我们需要根据对象不同的生命周期来使用不同的分配方式
分代GC
引用计数
- 每个对象都有一个与之关联的引用数目
- 对象存活的条件:当且仅当引用数大于0
- 优点:
- 内存管理的操作被平摊到程序执行过程当中
- 内存管理不需要了解runtime的实现细节:C++智能指针(smart pointer)
- 缺点:
- 维护引用计数开销较大:通过原子操作保证对引用计数操作的原子性和可见性
- 无法回收环形数据结构 - weak reference
- 内存开销:每个对象都引入的额外内存空间存储引用数目
- 回收内存时依然可能引发暂停
Go内存分配
分块
- 目标:为对象在heap上分配内存
- 提前将内存分块
- 调用系统调用mmap() 向OS申请一大块内存
- 先将内存划分成大块
- 再将大块继续划分成特定大小的小块,用于对象分配
- noscan mspan:分配不包含指针对象 -- GC不需要扫描
- scan mspan:分配包含指针的对象 -- GC需要扫描
- 对象分配:根据对象的大小,选择最合适的块返回