[ 后端与 Go | 青训营笔记 ]

79 阅读7分钟

[ 后端与 Go | 青训营笔记 ]

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

课前:相关术语解读

自动内存管理

  • Auto memory management: 自动内存管理

  • Grabage collction: 垃圾回收

  • Mutator: 业务线程

  • Collector: GC 线程

  • Concurrent GC: 并发 GC

  • Parallel GC: 并行 GC

  • Tracing garbage collection: 追踪垃圾回收

    • Copying GC: 复制对象 GC
    • Mark-sweep GC: 标记-清理 GC
    • Mark-compact GC: 标记-压缩 GC
  • Reference counting: 引用计数

  • Generational GC: 分代 GC

    • Young generation: 年轻代
    • Old generation: 老年代

Go 内存管理及优化

  • TCMalloc
  • mmap() 系统调用
  • scan object 和 noscan object
  • mspan, mcache, mentral
  • Bump-pointer object allocation: 指针碰撞风格的对象分配

编译器和静态分析

  • 词法分析
  • 语法分析
  • 语义分析
  • Intermediate representation (IR) 中间表示
  • 代码优化
  • 代码生成
  • Control flow: 控制流
  • Data flow: 数据流
  • Intra-procedural analysis 过程内分析
  • Inter-procedural analysis: 过程间分析

Go 编译器优化

  • Function inlining: 函数内联
  • Escape analysis: 逃逸分析

课内:Go 内存管理详解

高性能 Go 语言发行版优化的内存管理优化,自动内存管理与 Go 内存管理知识,可行性的优化建议。

重点

  • 内存管理优化;
  • 编译器优化;
  • 自动内存管理和 Go 内存管理机制;
  • 编译器优化的基本问题和思路。

细节

为什么要优化

  • 提升软件系统处理能力;
  • 提供用户体验;
  • 资源高效利用。

image-20230119140929827

性能优化的层面

  • 业务代码 -> SDK -> 基础库 -> 语言运行时 -> OS;

  • 业务层优化;

    • 特定场景,具体问题,具体分析;
    • 性能收益大。
  • 语言运行时优化;

    • 解决更通用的性能问题;
    • 考虑更多场景。
  • 数据驱动;

    • 自动化性能分析工具;
    • 依靠数据而非猜测;
    • 实现优化最大瓶颈。

image-20230119141547149

自动内存管理

概念

动态内存

  • 程序运行时根据需求动态分配的内存:malloc();

自动内存管理:垃圾回收,系统管理动态内存

  • 避免手动管理;
  • 保证内存使用的正确性和安全性;

三个任务

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

image-20230119143122284

image-20230119143235739

Grabage collction: 垃圾回收

追踪垃圾回收

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

  • 标记根对象;

    • 静态变量,全局变量,常量,线程栈;
  • 标记:找到可达对象;

    • 从根对象出发,找到所有可达对象;
  • 清理:所有不可达对象;

    • 存活对象复制到另外的内存空间(Copying GC);
    • 死亡对象内存标记为可分配(Mark-sweep GC);
    • 移动并整理存活对象(Mark-compact GC);
  • 根据对象的生命周期,使用不同的标记和清理策略。

Generational GC: 分代 GC

分代假说

  • intuition:很多对象分配后就不再使用;
  • 对象的年龄:经历 GC 的次数;
  • 目的:对年轻和年老对象,制定不同的 GC 策略,降低整体内存开销;
  • 不同年龄对象处于 heap 的不同区域。

image-20230119145602869

Reference counting: 引用计数
  • 每个对象都有一个与之关联的引用数目;
  • 对象存活条件:当且仅当引用数大于 0;

image-20230119145905111

  • 优点

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

    • 维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性;
    • 无法回收环形数据结构;
    • 内存开销大;
    • 内存回收时依然可能引发暂停;

image-20230119150324893

Go 内存管理及优化

Go 内存分配

分块

  • 目标:为对象在 heap 上分配内存;
  • 提前将内存分块;

image-20230119153427620

缓存

  • 每个 p 包含了一个 mcache 用于快速分配,用于为绑定与 p 上的 g 分配对象;
  • mcache 管理一组 mspan;
  • 当 mcache 中的 mspan 分配完毕,向 mcentral 申请带有未分配块的mspan;
  • 当 mspan 中没有分配对象,mspan 会被缓存在 mcentral 中,而不是立即释放并归还给 OS;

image-20230119154014090

Go 内存管理优化
  • 对象分配是非常高频的操作:每秒分配 GB 级别内存;

  • 小对象占比较高;

  • Go 内存分配比较耗时;

    • 分配路径长;
    • 对象分配函数是最频繁调用的函数之一;

优化方案:Balanced GC

image-20230119154434253

  • GAB 对于 Go 内存管理来说是一个大对象;
  • 本质:将多个小对象的分配合并成一次大对象的分配;
  • 问题:GAB 的对象分配方式会导致内存被延迟释放;

image-20230119154800541

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

    • 当 GAB 的总大小超过一定阈值的时候,将其中存活对象复制到另外分配的 GAB 中;
    • 原先的 GAB 可以释放,避免内存泄漏;
    • 本质:用 copying GC 的算法管理小对象;

image-20230119155043330

编译器和静态分析

基本介绍

编译器的结构

image-20230119155304248

数据流与控制流

静态分析

  • 不执行程序代码,推导程序的行为,分析程序的性质;
  • 控制流:程序执行的流程;
  • 数据流:数据在控制流上的传递;
  • 通过分析控制流和数据流,可以知道更多关于程序的性质;
  • 根据性质优化代码;

image-20230119155925999

过程内和过程间分析
  • 过程内分析:仅在函数内部进行分析;
  • 过程间分析:考虑函数调用时参数的传递和返回值的数据流和控制流;

image-20230119160132390

Go 编译器优化

  • 为什么做编译器优化

    • 用户无感知,重新编译即可获得性能收益;
    • 通用性优化;
  • 现状

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

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

    • 函数内联;
    • 逃逸分析;
    • 默认栈大小调整;
    • 边界检查消除;
    • 循环展开;
函数内联
  • 内联:将被调用的函数的函数体的副本替换到调用位置上,同时重写代码以反映参数的绑定;

  • 优点

    • 消除函数调用开销;
    • 将过程间转为过程内分析,帮助其他优化;

image-20230119161347317

image-20230119161358347

  • 缺点

    • 函数体变大;
    • 编译生成的 Go 镜像变大;
  • 函数内联在大多数清况下是正向优化;
逃逸分析

image-20230119161554460

  • 分析代码中指针的动态作用域;指针在何处可以被访问;

  • 思路

    • 从对象分配处出发,沿着控制流,观察对象的数据流;

    • 若分析指针 p 在当前作用域 s;

      • 作为参数传递给其他函数;
      • 传递给全局变量;
      • 传递给其他的 goroutine;
      • 传递给已逃逸的指针指向的对象;
    • 则指针 p 指向的对象逃逸出 s;

  • Beast mode:函数内联拓展了函数边界,更多对象不逃逸;

  • 优化:未逃逸对象可以在栈上分配

    • 对象在栈上分配和回收很快;
    • 减少在 heap 上的分配,降低 GC 的负担;

总结

对相关术语有所了解,对自动内存分配有了具体的了解,明白了内存优化的好处。