高性能 Go 语言发行版优化与落地实践 | 青训营笔记

94 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

引言

追求极致性能

  • 性能优化是什么?

    • 提升软件系统处理能力,减少不必要的消耗,充分发掘计算机算力
  • 为什么要做性能优化

    • 用户体验:带来用户体验的提升 ——让刷抖音更丝滑,让双十一购物不再卡顿
    • 资源高效利用:降低成本,提高效率——很小的优化乘以海量机器会是显著的性能提升和成本节约

自动内存管理(垃圾回收)

由程序语言的运行时系统回收动态内存

追踪垃圾回收

  • 回收条件:指针指向关系不可达的对象

  • 标记:找到可达对象

    • 求指针指向关系的传递闭包;从根对象出发,找到所有可达对象--标记所有通过指针能找到的对象
  • 清理:所有不可达对象

    • Copying GC —— 将存活对象复制到另外的内存空间
    • Mark-sweep GC ——将死亡对象的内存标记为“可分配”
    • Mark-compact GC ——移动并整理存活对象(相当于压缩)

image-20220517110640613.png

  • 根据对象的生命周期,使用不同的标记和清理策略

分代GC

争对年轻的老年的对象,制定不同的GC策略,降低整体内存管理的开销

  • 年轻代

    • 存活对象很少,可以采用copying collection

    • GC吞吐率很高

image-20220517111022564.png

  • 老年代

    • 对象趋向于一直活着,反复复制开销较大
    • 可以采用mark-sweep collection

image-20220517111136142.png

引用计数

  • 每个对象都有一个与之关联的引用数目 即记录它的入度

  • 优点

    • 内存管理的操作被平摊到程序执行过程中
    • 内存管理不需要了解runtime的实现细节
  • 缺点

    • 维护引用计数开销大:通过原子操作保证对引用计数操作的原子性和可见性

    • 无法回收环形数据结构 ——weak reference

    • 内存开销:每个对象都引入的额外内存空间存储引用数目

    • 回收内存依然可能引发暂停(链路太长)

image-20220517111634797.png

Go内存管理及优化

Go内存分配

分块

  • 目标:为对象在heap上分配内存
  • 提前将内存分块
  • 根据对象的大小,分配块

缓存

image-20220517114238709.png

Go内存管理优化

分析

  • 对象分配是非常高频的操作
  • 小对象占比比较高
  • Go内存分配比较耗时

优化方案 Balanced GC

  • 每个g都绑定一大块内存(1kB)称为GAB
  • GAB用于noscan类型的小对象分配: < 128 B
  • 使用三个指针维护GAB base end top

  • Bump pointer(指针碰撞)分格对象分配

    • 无须和其他分配请求互斥

    • 分配动作简单高效

image-20220517114819884.png

形如队列的方式插入,添加一个标记位区分不同对象

  • GAB对于Go内存管理来说是一个对象,会导致内存被延迟释放——其中一个对象没有释放,整个GAB都不呢个释放

  • 方案:移动GAB存活的对象

    • 设定阈值,用copying GC的算法管理小对象

编译器和静态分析

编译器的结构

image-20220517115449248.png

静态分析

image-20220517115616651.png

过程内分析和过程间分析

  • 过程内分析

    • 仅在函数内部进行分析
  • 过程间分析

    • 考虑过程调用参数传递和返回值的数据流和控制流
  • 过程间分析联合求解,比较复杂

Go编译器优化

  • why?

    • 用户无感知,重新编译获得性能收益
  • now

    • 采用的优化少
    • 编译时间较短,没有进行较复杂的代码分析和优化
  • 编译优化的思路

    • 场景:面向后端长期执行任务
    • Tradeoff:用编译时间换取更高效的机器码
  • Beast mode

    • 函数内联
    • 逃逸分析
    • 默认栈大小调整
    • 边界检查消除
    • 循环展开

函数内联

image-20220517150612810.png

Beast Mode

  • 调整函数内联的策略,使更多的函数被内联,更多对象不逃逸
  • 未逃逸的对象可以在栈上分配
  • 对象在栈上分配和回收很快,移动sp
  • 减少在heap上的分配,降低GC负担

逃逸分析

image-20220517151024994.png