性能优化|青训营笔记

115 阅读4分钟

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

优化

  • 性能优化:提升软件系统处理能力,减少不必要的消耗,充分发掘计算机算力
  • 为什么要做性能优化
    • 用户体验:带来用户体验的提升
    • 资源高效利用:降低成本,提升效率
  • 优化 TODO:
    • 测试用例要覆盖尽可能多的场景,方便回归
    • 使用文档向用户说明优化做了什么,没做什么,能达到什么样的效果
    • 通过选项控制是否开启优化保证隔离性
    • 使用必要的日志输出保证优化的可观测性
  • 数据驱动:
    • 一定要依托于数据而非猜测
    • 使用自动化性能分析工具
    • 首先优化最大瓶颈
  • 性能优化的层面:
    • 业务代码
      • 针对特定场景,做到具体问题具体分析
      • 容易获得较大性能收益
    • SDK(Commands + APIs + New APIs)
      • 解决更通用的性能问题
      • 考虑更多场景
      • 使用 Tradeoffs
      • 在保证接口稳定的前提下改进具体实现
    • 基础库
    • 语言运行时
    • OS

自动内存管理

概念

  • 管理的是动态内存而不是静态内存

  • 由程序语言的运行时系统管理动态内存,保证内存使用的正确性和安全性

    • 例如避免了 C 语言中的 double free 和 use after free 问题
  • 任务:

    • 为新对象分配空间
    • 找到存活对象
    • 回收死亡对象的内存空间
  • 两种线程:

    • Mutator:业务线程,分配新对象,修改对象指向关系
    • Collector:GC 线程,找到存活对象并回收死亡对象的内存空间
      • 必须能够感知对象指向关系的改变,避免部分对象标记错误
  • 三种算法(区别在于停止的时候 GC 线程的个数以及是否可以同时执行):

    • Serial GC:只有一个 Collector
    • Parallel GC:支持多个 collectors 同时回收的 GC 算法
    • Concurrent GC:mutator 和 collector 可以同时执行
  • 评价 GC 算法

    • 安全性(基本要求):不能回收存活的对象
    • 吞吐率:1GC时间程序执行总时间1-\frac{GC 时间}{程序执行总时间},也即花在 GC 上的时间
    • 暂停时间:业务是否感知到暂停了
    • 内存开销:GC 元数据开销,额外开辟的空间大小

追踪垃圾回收

  • 被回收的条件是指针指向了关系不可达的对象
  • 步骤:
    1. 标记根对象
      • 静态变量 + 全局变量 + 常量 + 线程栈
    2. 找到可达对象并标记
      • 求指针指向关系的传递闭包
    3. 找到所有不可达对象并清理
  • 三种清理方法:
    • 将存活对象复制到另外的内存空间(copying GC)
    • 将死亡对象的内存标记为可分配:下次分配空间的时候直接使用被标记为可分配的空间(mark-sweep GC)
    • 移动并整理存活对象:将所有存活对象挪动到最前端(Mark-compact GC)
  • 根据对象的生命周期,使用不同的标记和清理策略

分代 GC

  • most object die young

  • 对于不同对象,经历过 GC 的次数就是她们的年龄,而对于年轻和老年的对象,通过制定不同的 GC 策略,降低了整体内存管理的开销

    • 年轻代:
      • 常规的对象分配
      • 使用 copying collection
      • GC 吞吐率较高
    • 老年代:
      • 对象趋向于一直活着,反复复制开销较大
      • 采用 mark-sweep collection
  • 不同年龄的对象处于 heap 的不同区域

引用计数

  • 每个对象都有一个与之关联的引用数目
  • 对象的存活条件是当且仅当引用数目大于 0
  • 优点:
    • 内存管理的操作被平摊到了程序执行的过程中
    • 内存管理不需要了解 runtime 的实现细节
  • 缺点:
    • 由于需要通过原子操作保证对引用计数操作的原子性和可见性,导致了维护引用计数的开销较大
    • 无法回收环形数据结构
    • 每个对象都引入了额外的内存空间来存储引用数目,因此会造成一定的内存开销
    • 尽管平摊到了程序执行过程,但是如果结点很多,回收内存的时候依然可能引发暂停

Go 内存管理及优化

  • 目标:为对象在 heap 上分配内存
  • 分块:
    • 调用系统调用 mmap 向 OS 申请一大块内存
    • 先将内存划分成大块,称为 mspan
      • noscan mspan:分配不包含指针的对象(GC 不需要扫描)
      • scan mspan:分配包含指针的对象(GC 需要扫描)
    • 再将大块继续划分为特定大小的小块,用于对象分配
  • 对象分配:根据对象的大小,选择最合适的块返回