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

65 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

自动内存管理

malloc() 分配的内存

优点

  • 避免手动管理,专注于业务逻辑
  • 保证内存使用的正确性和安全性

相关概念

  • Mutator:业务线程,分配新对象,修改对象指向关系
  • Collector: GC线程,找到存活对象,回收死亡对象的内存空间
  • Serial GC:只有一个collector
  • Parallel GC:支持多个collectors同时回收的GC算法
  • Concurrent GC: mutator(s)和collector(s)可以同时执行
    • Collectors必须感知对象指向关系的改变!

评价方法

  • 安全性(Safety):不能回收存活的对象基本要求
  • 吞吐率(Throughput): 1- GC时间 / 程序执行总时间 花在GC上的时间
  • 暂停时间(Pause time): stop the world(STW) 业务是否感知
  • 内存开销(Space overhead) GC元数据开销

三个任务

  • 为新对象分配空间
  • 找到存活对象
  • 回收死亡对象的内存空间

追踪垃圾回收

  • 求指针关系的传递闭包:从根对象出发,找到所有可达对象
  • 清理方法
    • 将存活对象复制到另外的内存空间(Copying Gc)
    • 将死亡对象的内存标记为“可分配”(Mark-sweep Gc)
    • 移动并整理存活对象(Mark-compact GC)

分代GC

  • 针对年轻和年老的对象,指定不同的GC策略
    • 年轻
      • 存活对象很少
      • Copying Gc
      • 吞吐率很高
    • 老年
      • Mark-sweep Gc

引用计数

  • 每个对象关联一个引用数目

  • 优点

    • 内存管理的操作被平摊到程序执行过程中
    • 内存管理不需要了解runtime的实现细节:C++智能指针(smart pointer)
  • 缺点

    • 维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
    • 无法回收环形数据结构——weak reference
    • 内存开销:每个对象都引入的额外内存空间存储引用数目
    • 回收内存时依然可能引发暂停

内存管理优化

  • 小对象占比较高
  • Balance GC
    • 正常分配路径 g -> m -> p -> mcache -> mspan -> memory block -> return pointer
    • 优化:在g上绑定一个大内存GAB(1kb),使用指针碰撞风格分配(base,top,end),用于noscan类型的小对象分配
    • 问题:内存延迟释放
      • 设定阈值,超过阈值,将存活对象复制到另一个GAB中
      • copying GC

Go 编译器优化

函数内联

  • 优点
    • 消除函数调用开销,例如传递参数、保存寄存器等
    • 将过程间分析转化为过程内分析,帮助其他优化,例如逃逸分析
  • 缺点
    • 函数体变大,instruction cache (icache)不友好
    • 编译生成的Go镜像变大
  • 大多数都是正向优化
  • Go函数内联受到的限制较多
  • 逃逸分析
    • 分析指针的动态作用域,因为Go是允许在函数外访问变量的
    • 未逃逸的对象可以在栈上分配