性能优化及自动内存管理|青训营笔记

86 阅读3分钟

t01cf9f4d0824c26461.jpg

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


性能优化的层面

  • 业务层优化
    • 针对特定场景,具体问题,具体分析
    • 容易获得较大性能收益
  • 语言运行时优化
    • 解决更通用的性能问题
    • 考虑更多场景
    • Tradeoffs
  • 数据驱动
    • 自动化性能分析工具——pprof
    • 依赖数据而非猜测
    • 首先优化最大瓶颈

一、自动内存管理

  • 1.自动内存管理(垃圾回收):基于追踪
    • 避免手动内存管理
    • 保证内存使用的正确性和安全性:double-free problem(连续释放同一块内存),use-after-free problem(内存释放后又被使用)
  • 三个任务
    • 为新对象分配空间
    • 找到存活的对象
    • 回收死亡对象的内存空间
  • 相关概念
    • Mutator:业务线程,分配新对象,修改对象指向关系
    • Collector:GC线程,找到存活对象,回收死亡对象的内存空间
    • Serial GC:只有一个collector(有暂停)
    • Parallel GC:支持多个collectors同时回收的GC算法(有暂停)
    • Concurrent GC:mutator(s)和collector(s)可以同时执行,collectors必须感知对象指向关系的改变

image.png

  • 对象被回收的条件:指针指向关系不可达的对象
  • 清理:所有不可达对象
    • 将存活对象复制到另外的内存空间(Copying GC)
    • 将死亡对象的内存标记为“可分配”(Mark-sweep GC)
    • 移动并整理存活对象 (Mark-compact GC)
  • 根据对象的生命周期,使用不同的标记和清理策略
  • 2.分代GC(Generational GC)
    • 年龄:经历过的GC次数
    • 年轻代:由于存活对象很少,可以采用Copying GC
    • 老年代:对象趋向于一直活着,反复复制开销较大,可以使用Mark-sweep GC
  • 3.引用计数
    • 每个对象都有与之关联的引用数目
    • 对象存活条件:当且仅当引用数大于0
    • 优点:
      • 内存管理的操作被平摊到程序执行过程中
      • 无需了解runtime的实现细节
    • 缺点:
      • 开销大:通过原子操作保证原子性和可见性
      • 无法回收环形数据结构
      • 内存开销大:每个对象引入额外空间存储引用空间
      • 回收内存时可能引发暂停

二、Go内存管理及优化

Go内存分配

  • 分块
    • 提前将内存分块
    • noscan mspan:分配不包含指针的对象——GC不需要扫描
    • scan mspan:分配包含指针的对象——GC需要扫描
  • 缓存
    • TCMalloc:thread caching

image.png

Go内存管理优化

  • 对象分配是高频操作,每秒分配GB级别
  • 小对象占比较高
  • Go内存分配比较耗时

优化方案

  • 每个g绑定一大块内存(1kb),goroutine allocation buffer(GAB)
  • GAB用于noscan类型的小对象分配 <128 B

image.png

三、编译器和静态分析

编译器结构

image.png

静态分析:不执行程序代码,推导程序行为,分析程序性质

  • 控制流(control flow):程序执行流程
  • 数据流(data flow):数据在控制流上的传递

过程内分析和过程间分析

  • 过程内分析:仅在函数内部进行分析
  • 过程间分析:考虑过程调用时参数传递和返回值的数据流和控制流
  • 过程间分析需要同时分析数据流和控制流——联合求解,比较复杂

四、Go编译器优化

函数内联(Inlining)

  • 内联:将被调用的函数体(callee)的副本替换到调用位置(caller)上,同时重写代码以反映参数的绑定
  • 优点
    • 消除函数调用开销
    • 将过程间分析转换成过程内分析,帮助其他优化,如逃逸分析

逃逸分析:分析代码中指针的动态作用域:指针在何处可以被访问,未逃逸的对象可以在栈上分配