高性能 Go 语言发行版优化与落地实践
- 优化
- 内存管理优化
- 编译器优化
- 背景
- 自动内存管理和 Go 内存管理机制
- 编译器优化的基本问题和思路
- 实践:工业界实际遇到的性能问题以及优化方案
性能优化的层面
性能优化与软件质量
回归测试:在新的代码实现中,使用之前的测试用例进行测试,比较测试结果
隔离:可以终止是否开启优化,当不开启时输出的结果应该与之前一样
课程目录
- 自动内存管理
- Go 内存管理及优化
- 编译器和静态分析
- Go 编译器优化
本节介绍自动内存管理相关的知识
自动内存管理
简介
-
动态内存:程序在运行时根据需求动态分配的内存:malloc()
-
自动内存管理(垃圾回收):由程序语言的运行时系统回收动态内存
- 避免手动内存管理,让程序员可以专注于实现业务逻辑
- 保证内存使用的正确性和安全性:double-free problem, use-after-free problem (释放一块内存后又释放一次、在释放一块内存后又去访问它)
- 三个任务
- 为新对象分配空间
- 找到存活对象
- 回收死亡对象的内存空间
相关概念
Mutator: 业务线程,分配新对象,修改对象指向关系
Collector:GC 线程,找到存货对象,回收死亡对象的内存空间
Serial GC:只有一个 collector
Parallel:支持多个 collectors 同时回收的 GC 算法
Concurrent GC:mutator(s) 和 collector(s) 可以同时执行(Collectors 必须感知对象指向关系的改变,以免释放错对象的内存空间)
GC 算法评价指标
追踪垃圾回收(Tracing garbage collection)
copying GC:将存活对象整理到另外的内存空间(整理内存碎片,提供空间利用率)
Mark-sweep GC:将死亡对象占用的内存空间标记为可分配,使用 free list 管理空闲内存
Compact GC:将存活对象拷贝到空间最开始的地方(压缩空间,释放出大块可用的内存空间)
分代 GC(Generational GC)
- 分代假说(Genarational hypothesis):most objects die young
- Intuition:很多对象在分配出来后很快就不再使用了
- 每个对象都有年龄:经历过 GC 的次数
- 目的:针对年轻和老年的对象,制定不同的 GC 策略,降低整体内存管理的开销
- 不同年龄的对象处于 heap 的不同区域
年轻代(Young generation)
- 常规的对象分配
- 由于存活对象很少,可以采用 copying collection(将零星的几个存活对象复制到另一块内存空间的开头,从而释放出更多的内存空间)
- GC 吞吐率很高
老年代(Old generation)
- 对象趋向于一直活着,反复复制开销较大
- 可以采用 mark-sweep collection(这类对象长时间存活,可以简单的将其标记为存活对象后放在原处,其余剩余空闲空间交由空闲列表统一管理,存活对象不需要压缩和转移(浪费 cpu 资源))
- 引用计数(Reference counting)
- 每个对象都有一个与之关联的引用数目
- 对象存活的条件:当且仅当引用数大于 0
优点
- 内存管理的操作被平摊到程序执行过程中
- 内存管理不需要了解 runtime 的实现细节:c++智能指针(smart pointer)
缺点
- 维护引用计数的开销较大(使用原子操作保证对引用计数操作的原子性和可见性)
- 无法回收环形数据结构(解决访达:weak reference)
- 内存开销:每个对象都需要引入额外内存空间来存储引用数目
- 回收内存时依然可能引发暂停(比如回收比较大的引用关系时)
小结
- 自动内存管理的背景和意义
- 概念和评价方法
- 追踪垃圾回收
- 引用计数
- 分代 GC
- 自动内存管理技术的不足之处 (Low-Latency, High-Throughput Garbage Collection)