Go 内存管理及优化 | 青训营笔记

98 阅读4分钟

Go 内存管理及优化

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

1.自动内存管理(Auto memory management)

背景

动态内存:malloc()
自动内存管理:运行时系统管理动态内存。
保证内存使用正确性和安全性:
double-free problem:同一个指针free两次。只要free一个指向堆内存的指针都有可能产生可以利用的漏洞。
use-after-free problem:在free之后使用。
三个任务:

  • 为新对象分配空间
  • 找到存活对象
  • 回收死亡对象的内存空间
    基本概念:
  • Mutator:业务线程,分配新对象,修改对象指向关系。
  • Collector:GC线程,找到存活对象,回收死亡对象的内存空间。
  • Serial GC:只有一个collector
  • Parallel GC:支持多个collectors同时回收的GC算法
  • Concurrent GC:mutator(s)和collector(s)可以同时执行 (必须感知对象指向关系的改变)

Tracing garbage collection——追踪垃圾回收(1)

  • 对象被回收条件:指针指向关系不可达的对象
  • 标记根对象:
    • 静态变量、全局变量、常量、线程栈等
  • 标记:找到可达对象
    • 求指针指向关系的传递闭包:从根对象出发,找到所有可达对象
  • 清理:所有不可达对象
    • 将存活对象复制到另外内存空间(Copying GC)
    • 将死亡对象的内存标记为“可分配”(Mark-sweep GC)
    • 移动并整理存活对象 (Mark-compact GC)
  • 根据对象的生命周期,使用不同的标记和清理策略

Generational GC——分代GC

  • 年龄:经历过GC的次数
  • 年轻代:
    • 常规分配
    • copying collection
    • GC吞吐率高
  • 老年代:
    • 对象趋向于一直活着,反复复制开销大
    • 采用mark-swaap collection

Reference couting——引用计数(2)

  • 每个对象都有一个与之关联的引用计数
  • 对象存活的条件:当且仅当引用计数大于0
  • 优点
    • 内存管理的操作被平摊到程序执行过程中
    • 内存管理不需要了解runtime的实现细节
  • 缺点
    • 维护引用计数的开销大
    • 无法回收环形数据结构
    • 内存开销:每个对象都引入额外内存空间存储引用数目
    • 回收内存时依然可能引发暂停

2.Go内存管理及优化

Go内存分配

分块

  • 目标:为对象在heap上分配内存
  • 提前将内存分块
    • 调用mmap()向OS申请一大块内存
    • 将大内存分成大块,mspan
    • 大块内存分配成特定块,用于对象分配
    • noscan mspan:不包含指针对象
    • scan mspan:包含指针对象
  • 根据对象大小,分配特定块

缓存——多级缓存

Go内存管理优化

问题:

  • 对象分配高频
  • 小对象占大多数
  • 内存分配消耗CPU资源

优化——Balanced GC

  • 每个g绑定一块大内存(1kb),goroutine allocation buffer(GAB)
  • GAB用于noscan类型的小对象分配: <128B 细节
  • GAB对Go内存管理来说是一个大对象
  • 本质:将多个小对象的分配合并为一次大对象分配
  • 问题:内存延迟释放
  • 方案:移动GAB中存活的对象(超过阈值,复制,释放、copying GC)

3.编译器优化思路

基本介绍

编译器结构

静态分析

不执行代码,推导程序行为

数据流和控制流

过程内和过程间

过程间分析存在问题

  • 通过数据流分析得知i具体类型
  • 根据i的类型,产生新的控制流
  • 数据流和数据流同时分析

4. Go编译器优化

背景

特点:用户无感知,通用性优化 现状:采用优化少,编译时间短 思路:用时间换效率 Beast mode

函数内联(inline)

类似于C++的内联函数。
优点:消除函数调用开销,将过程间分析转换为过程内分析
缺点:函数体变大,Go镜像大
策略:调用和被调函数规模

逃逸分析

分析代码中指针的动态作用域 大致思路

  • 从对象分配处出发,沿着控制流,观察数据流。若发现指针 p 在当前作用域 s:

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

引用

juejin.cn/course/byte…