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

60 阅读3分钟

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

性能优化

  • 是什么: 提高程序效率,减少不必要消耗

  • 为什么:用户体验,资源高效利用

  • 怎么做:

    • 业务层优化:针对特定场景,具体问题,具体分析
    • 语言运行时优化:解决更通用的性能问题
    • 数据驱动:自动化性能分析工具 —— pprof
    • 保证稳定的前提下进行优化,通过测试驱动,用完善的文档告诉用户优化的效果

自动内存管理

  • 自动内存管理

    目标:动态内存分配,查找存活对象,回收

    Mutator:业务线程,分配对象,修改对象指向关系

    Collector:GC线程,找到存活对象,回收死亡对象

    Serial GC:单个 collector,会有 STW

    Parallel GC:多个 collector,会有 STW

    Concurrent GC:可以同时指向两种线程,无STW

    追踪垃圾回收:

    • 可达性分析,回收不可达的对象

    过程:

    1、首先标记根对象(常量,栈内临时变量,静态变量)

    2、标记:通过对象之间的关系层层搜寻出可以达到的对象

    3、清理:将不可达的对象清理

    • 引用计数法

    第二种对象回收标记法是引用计数法,对于每个对象都有一个关联的值记录被引用的数目,当引用为0,就是可回收对象。

    对于两种方法的比较:

    • 追踪垃圾回收

      • 通过可达性分析,标记出存活对象,清理不可达对象
      • 优点是不需要维护额外数据
      • 缺点是会有 STW,对性能影响比较大。
    • 引用计数法

      • 每个对象有一个引用计数,当引用计数为0将会被清理
      • 优点是内存管理被平摊到程序执行中
      • 缺点是难以解决循环引用,维护引用计数开销较大

    清理方式有3种:

    1、标记复制:将存活对象复制到另一块空间,原空间就变为可使用空间

    2、标记整理:将存活对象复制到空间的同一端,这样剩余空间就变为可用空间,是对标记复制的优化

    3、标记清除:遍历空间,将死亡对象空间标为可分配,用 free list 管理。

    • generational GC | 分代 GC

    GC 年龄:就是一个对象经历过的GC次数

    年轻代:使用标记整理

    老年代:使用标记清除

Go 内存管理及优化

  • 首先通过系统调用 mmap 申请内存
  • 将内存都划分为大小为 8K 的mspan
  • 每个 mspan 内划分等大小的小块,不同 mspan 小块大小可以不同,用于分配给对象内存
  • mspan 分为 scan 和 noscan,其中 scan mspan 具有指针对象,会被 GC 扫描
  • 通过 mcache 分配内存
  • mcache 中每种分块大小的 mspan 都有一组
  • 如果 mcache 中的 msapn 满了就会换新一块进来
  • Go 编译器优化

    用户无感知,直接获得收益,通用性

    • 思路

    对于长期使用的代码,通过完善的编译优化提高执行速度

    • 函数内联

      • 消除函数调用开销(传参,寄存器)
      • 过程间分析转为过程内分析
      • 函数体变大,对instruction cache不好
      • 对编译生成的 Go 镜像变大
    • 逃逸分析:逃逸分析就是分析代码中指针的动态作用域,也就是指针可以在何处被访问

      • 对于未逃逸的对象可以在栈上分配,对象在栈上分配和回收非常快
      • 减少在 heap 上的分配,降低 GC 负担