这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
高性能Go语言发行版优化与落地实践
性能优化是什么?
- 提升软件系统的处理能力,减少不必要的消耗 为什么要进行性能优化?
- 提升用户体验
- 降低成本,提高效率,对资源的高效利用
性能优化的两个层面
-
业务层优化
- 针对特定场景,具体问题具体分析
- 容易获得较大性能收益
-
语言运行时优化
- 解决更通用的性能问题
-考虑更多场景
-Tradeoffs
SDK的两个层面
- 解决更通用的性能问题
-考虑更多场景
-Tradeoffs
1 自动内存管理
-1.1 动态内存
管理的是动态内存,重程序在运行时根据需求动态分配的内存
- 避免手动内存管理,可以专注于业务逻辑
- 保证内存使用的正确性和安全性
- 自动内存管理的三个任务
-
为新对象分配空间 -
找到存活对象 -
回收死亡对象的内存空间
-
- 自动内存管理的相关概念
第一个图中会有暂停,只有一个GC;第二个支持多个Colllector进行回收,在暂停时会同时回收;第三个Mutator和Collector可以同时进行,等到GC做完之后在把Collector休眠。
-
Concurrent GC的挑战?Collector必须感知对象指向关系的改变 - 评价GC算法的几个方面
- 1.2追踪垃圾回收
- 对象被回收的条件:指针指向关系不可达的对象
- 三个步骤:
- 1 标记根对象(静态变量、全局变量、常量、线程栈等)全部标记为存活
- 2 标记所有可达对象(求指针指向关系的传递闭包)
- 3 清理所有不可达对象
- 将存活对象复制到另外的内存空间(Copying GC),原先的空间可以直接进行对象分配
- 将死亡对象的内存标记为“可分配”(Mark-sweep GC),使用free list管理可分配的空间
- 移动并整理存活对象(Mark-compact GC),将存活对象复制到同一块内存区域的开头
- 根据对象的生命周期,使用不同的标记和清理策略
- 1.3 分代GC (Generational GC)
- 分代假说:most objects die young
- 每个对象都有一个年龄:经历GC的次数,对年轻和年老的对象制定不同的GC策略,降低整体内存管理的开销
- 不同年龄的对象处于heap的不同区域
- 年轻代
- 由于存活对象很少,可以采用copying collection
- GC吞吐率高
- 年老代
- 对象趋向于一直存活,反复复制开销大
- 可以采用mark-sweep collection
- 1.4 引用计数
- 每个对象都有一个关联的引用数目
- 对象存活的条件是引用数大于0
- 优点
- 内存管理的操作被平摊到程序执行过程中
- 内存管理不需要了解routime的实现细节
- 缺点
- 维护引用计数的开销大
- 无法回收环形数据结构
- 需要额外的内存开销,存储引用数
- 回收内存时依然有可能引发暂停
2 GO内存管理及优化
- 分块
- 缓存
如果mcentral也分配完毕,再向mheap申请
- 字节的优化方案 Balanced GC
3 编译器和静态分析
-
编译器的结构
- 分析部分(前端)
- 综合部分(后端)
-
静态分析
- 不执行程序代码,推导程序的行为,分析程序的性质
- 控制流:程序执行的流程
- 数据流:数据在控制流上的传递 通过分析控制流和数据流,我们可以知道更多关于程序的性质
-
过程内分析和过程间分析
- 过程内分析:仅在函数内部进行分析
- 过程间分析:考虑函数调用时参数传递和返回值的数据流和控制流
- 为什么说过程间分析是个问题?过程间分析需要同时分析控制流和数据流,联合求解,比较复杂。
4 Go编译器优化
- 为什么做编译器优化?
- 用户无感知,只要重新编译就能获得性能收益
- 是通用性的优化
- 现状
- 由于go的编译器都想缩短时间,采用的优化少,编译时间较短,没有进行复杂的代码分析和优化
- 编译优化的思路:
- 场景:面向后端长期执行任务
- Tradeoff:权衡,用编译时间换取更高效的机器码
- 字节的Beast Mode产品(寄生在go SDK)
- 函数内联
- 逃逸分析
- 默认栈大小调整
- 边界检查消除
- 循环展开 ……
- 函数内联
- 将被调用函数的函数体(callee)的副本替换到调用位置上(caller),同时重写代码以反映参数的绑定
- 优点:
- 消除函数调用开销,如传参等
- 将过程间分析转化为过程内分析,帮助其他优化,如逃逸分析
- 缺点:
- 函数体变大
- 编译生成的Go镜像变大
- 函数内联在大多数情况是正向优化的
- 可以使用 micro-benchmark快速验证性能优化
- 逃逸分析
- 分析代码中指针的动态作用域:指针在何处可以被访问
老师的参考文献
- The Garbage Collection Handbook -- the art of automatic memory management
是自动内存管理领域的集大成之作。把自动内存管理的问题、动机、方案、以及最新研究进展和方向进行了非常详尽的阐述。整个书很好读,参考文献非常充实,推荐大家阅读英文版。
- JEP 333: ZGC: A Scalable Low-Latency Garbage Collector openjdk.java.net/jeps/333
是目前 HotSpot JVM 上 pauseless GC 实现的 proposal,可以看作 GC 领域比较新的工程方面的进展。
- 数据密集型应用系统设计 Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems
通过例子带大家理解互联网产品需要解决的问题以及方案。
- 编译原理 The Dragon book, Compilers: Principles, Techniques, and Tools
在编译器前端着墨较多。本书第二版的第九章 机器无关优化,推荐大家反复仔细阅读。这一章主要讲述的是编译优化中常见的数据流分析问题,建议大家跟随书本仔细推导书中的例子,会帮助你对数据流分析有个大致的认识。这一章给出的引用文献大多是编译和静态分析领域非常有影响力的论文,有兴趣的同学可以阅读。
- 编译原理 Principles and Techniques of Compilers silverbullettt.bitbucket.io/courses/com…
南京大学编译原理课程。
- 静态程序分析 Static Program Analysis pascal-group.bitbucket.io/teaching.ht…
南京大学静态程序分析课程。参考文献 4 数据流分析读不懂的地方可以参考本课程的课件。
- 编译器设计 Engineering a Compiler
在编译器后端优化着墨较多。可以帮助大家理解后端优化的问题。
- JVM Anatomy Quark #4: TLAB allocation shipilev.net/jvm/anatomy…
Goroutine allocation buffer (GAB) 的优化思路在 HotSopt JVM 也能找到类似的实现。
- Constant folding, en.wikipedia.org/wiki/Consta…
常量折叠数据流分析。
- Choi, Jong-Deok, et al. "Escape analysis for Java." Acm Sigplan Notices 34.10 (1999): 1-19.
逃逸分析的 Java 实现。
- Zhao, Wenyu, Stephen M. Blackburn, and Kathryn S. McKinley. "Low-Latency, High-Throughput Garbage Collection." (PLDI 2022). 学术界和工业界在一直在致力于解决自动内存管理技术的不足之处,感兴趣的同学可以阅读。