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

94 阅读4分钟

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

性能优化及自动内存管理

为什么要做性能优化?降低成本,提高效率

软件基本结构

  • 业务代码

    • 比较容易获得较大的性能收益
    • 依靠pprof数据驱动,首先优化最大的瓶颈
  • SDK

    • Go SDK 语言运行时优化
    • 全公司都要用,要考虑更多的场景
  • 基础库

  • 语言运行时

  • OS

软件优化

  • 质量非常重要
  • 保证接口稳定
  • 测试用例:尽可能覆盖更多的场景,方便回归
  • 文档:做了什么,没做什么,能达到怎样的效果
  • 隔离:通过选项控制是否开启优化
  • 可观测:必要的日志输出

自动内存管理

由程序运行时系统管理动态内存

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

  • Mutator 业务线程 分配新对象,改变对象指向关系

  • Collector GC线程 找到存活对象,回收死亡对象内存空间

  • Serial GC:只有一个Collector

    • 会有STW
  • Parallel GC 支持多个collecors 同时回收的GC算法

  • Concurrent GC:mutator和collector可以同时执行

评价GC算法

  • 安全性
  • 吞吐率 1 - GC时间/程序执行总时间
  • 暂停时间:STW 业务上是否感知
  • 内存开销GC 元数据开销

追踪垃圾回收

  • 标记根对象
  • 找到可达对象
  • 清理所有不可达对象

根据对象不同周期,选择不同的策略

分代GC

假设:大部分对象很快就回收了

对象的年龄:对象经过GC的次数

对象分为Young Generation 和 Old Generation

Young:采用复制的方法清理,很快就死了,复制到一块连续的内存去

Old:倾向于一直活着,采用mark sweep 机制清理

引用计数

引用计数清零,内存释放

C++智能指针

缺点:

  • 开销大,需要原子操作引用计数
  • 没法对付环形结构
  • 额外内存开销
  • 回收的时候又可能引发暂停

Go内存管理

在head上分配内存

提前将内存分块

  • 系统调用mmap()向OS申请一块大内存,例如4MB

  • 内存分为大块,每块8KB 称为mspan

    • mspan分为特定大小的小块 8B 16B 24B 用于对象分配
  • noscan mspan 不包含指针的对象 GC不需要扫描

  • scan mspan 包含指针的对象 GC需要扫描

  • 根据对象大小,选择最合适的块返回

缓存

每个P包含一个mcache用于快速分配,为绑定于p的g分配对象

mcache管理一组mspan

mcache的mspan分配完毕,向mcentral申请带有未分配块的mspan

mspan没有分配的对象,mspan会缓存在mcentral中,而不是立即释放还给OS

优化方案

对象分配是非常高频的操作,每秒GB级别

小对象占比较高

Go内存分配比较耗时 g->m->p-> mcache-> mspan -> memory block -> return pointer

字节方案 Balanced GC

小对象使用GAB分配,采用指针碰撞的方式,1KB的GAB,分配8B,TOP指针移动8B即可,有start,end两个指针来定界

缺点:一个8B小对象就能让整个GAB存活

方案:当GAB超过上限时,采用copy机制把两个GAB中的小对象copy到一个GAB里紧密排列

编译器和静态分析

编译器

  • 词法分析

  • 语法分析 抽象语法树

  • 语义分析

  • 中间表示

    • 主要集中优化后端:以下亮点
  • 代码优化

  • 代码生成 目标代码

静态分析

  • 控制流
  • 数据流

过程内分析:只在函数内

过程间分析:

Go编译器优化

函数内联

将被调用的函数体的副本替换到调用位置上,重写代码反映参数的绑定

优点:

  • 不用传递参数,保存寄存器
  • 过程间分析转化为过程内分析,有利于逃逸分析

缺点:

  • 函数体变大
  • 编译生成的Go镜像变大

Beast Mode

Go的interface和defer不太好内联

调整内联策略,使得更多函数被内联

降低开销,有助于逃逸分析

逃逸分析

若发现指针p在当前作用域s:

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

则说明p指向的对象逃逸出s

BeastMode:函数内联拓展了逃逸边界,更多对象不逃逸

优化:

  • 未逃逸对象栈上分配,移动sp指针就行
  • 减少在heap上分配,降低GC负担