Go 语言**高性能** | 青训营笔记

85 阅读4分钟

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

本文为青训营课程笔记,配合原课程食用效果更佳哦~

课程笔记

优化

一、优化是什么?

  1. 提升软件系统处理能力,减少不必要的消耗,充分发掘计算机算力

二、为什么优化?

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

三、优化点在哪?

  1. 业务层
    1. 针对特定场景,具体问题,具体分析
    2. 容易获得较大性能收益
  2. 内存管理
    1. GC垃圾回收优化
      1. 需要保证安全性和正确性的前提下才能优化

      2. 评价标注:安全性,吞吐率,暂停时间,内存开销

      3. 追踪垃圾回收

        1. 清理不可达对象,三种方式:

          1. 将存活对象复制到另外的内存空间 (Copying GC)
          2. 将死亡对象的内存标记为“可分配“ (Mark-sweep GC)
          3. 移动并整理存活对象 (Mark-compact GC)
        2. 分代假说(优化方式)

          1. 情况:很多对象在分配出来后很快就不再使用了

          2. 思路:对年轻和老年的对象,制定不同的 GC 策略,降低整体内存管理的开销

          3. 实现:不同年龄的对象处于 heap 的不同区域

            1. 年轻代使用copying collection
            2. 老年代使用mark-sweep collection
      4. 引用计数

        1. 清理引用数为0的对象

          1. 优点

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

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

        1. TCMalloc: thread caching
        2. 每个 p 包含一个 mcache 用于快速分配,用于为绑定于p 上的 g 分配对象
        3. mcache 管理一组 mspan
        4. 当 mcache 中的 mspan 分配完毕,向 mcentral 申请带有未分配块的 mspan
        5. 当 mspan 中没有分配的对象,mspan 会被缓存在mcentral 中,而不是立刻释放并归还给 OS
      2. 问题分析

        1. 对象分配是非常高频的操作:每秒分配 GB 级别的内存
        2. 小对象占比较高
        3. Go 内存分配比较耗时
    3. 优化方案:Balanced GC

      1. 使用三个指针base, end, top 维护 GAB:

      2. GAB 对于 Go 内存管理来说是一个对象

        • 本质:将多个小对象的分配合并成一次达对象的分配

        • 新的问题:GAB 的对象分配方式会导致内存被延迟释放

        • 方案:用 copying GC 移动 GAB 中存活的对象

  3. 编译器优化
    1. 过程内分析:仅在函数内部进行分析

    2. 过程间分析:考虑函数调用时参数传递和返回值的数据流和控制流

    3. 内联:将被调用函数的函数体 (callee) 的副本替换到调用位置 (caller) 上,同时重写代码以反映参数的绑定

      优点:消除函数调用开销,帮助逃逸分析

      缺点:函数体变大编,译生成的 Go 镜像变大

    4. Beast Mode

      • 针对问题:Go 函数内联受到的限制较多,内联策略非常保守
      • 解决方案:调整函数内联的策略
      • 效果:Go 镜像增加 ~10%
  4. 注意:

    1. 用数据说话,而非猜测优化效果
    2. 一定要在保证接口稳定的前提下改进
    3. 多写测试样例,保证稳定性和正确性

总结收获

通过样例与知识点结合的方式,学到了一些GC和编译器优化方式;

通过样例的讲解,更了解了具体的优化策略选择;

又是收获满满的一天