这是我参与「第五届青训营」笔记创作活动的第四天,本次课程讲解了go标准库中内存管理和编译器的优化,内存管理能理解,但是对于编译器部分不是很理解,后续重新看过之后再补充笔记
性能优化
- 是什么:提高软件系统处理能力,减少不必要的消耗,充分发掘算力
- 目的:资源高效利用,降低成本,提高效率
- 优化层面:
- 业务层优化
- 特定场景和问题
- 容易获得较大性能收益
- 语言运行时优化
- 解决更通用的性能问题
- 考虑更多场景
- 数据驱动
- 依赖数据进行优化
- pprof
- 优化最大瓶颈
- 业务层优化
- 实际操作对象 Go SDK
- goSDK 结构
- 优化时必须保证接口的稳定性
- 隔离:通过选项控制是否开启优化
- 测试用例尽可能多的场景
- 可观测性,必要的日志输出
- goSDK 结构
自动内存管理
- 基本概念
- 动态内存
- 程序在运行时动态分配的内存:malloc
- 自动内存管理:程序语言运行时系统动态内存
- 三个任务:
- 为新对象分配空间
- 找到存活对象
- 回收死亡对象空间
- 相关概念:
- Mutator:业务线程。分配新对象,修改对象指向
- Collector:GC线程,找到存活对象,回收死亡对象
-GC算法分类
- Serial GC:只有一个collector,有暂停
- parallel GC:多个collectors 同时回收,有赞停
- Concurrent GC:mutators和collectors同时指向
- 挑战
- 挑战
- 分类图解
- 评价指标
- 安全性:不能回收存活的对象
- 吞吐率:1-GC时间/执行总时间 GC更快
- 暂停时间:stop the world (STW) 业务能否感知
- 内存开销
- 常见技术
- 追踪垃圾回收
- 步骤:
- 标记根对象 静态,全局,常量,线程栈等
- 找到可达对象 找指针指向的传递闭包,从根对象出发
- 清理不可达对象
- 清理策略
- 将存活对象复制到另外的空间 Copying GC
- 将死亡对象的内存标记为可分配 Mark-sweep GC
- 移动并整理活对象 Mark-compact GC
- 将存活对象复制到另外的空间 Copying GC
- 清理策略
- 策略选择
- 分代GC
- 分代假说,大多数objects 很快消亡
- 每个对象都有年龄:经过GC的代数
- 目的:对年轻和老年 指定不同GC策略
- 不同年龄处于heap不同区域
- 年轻代
- 常规对象分配
- 存活对象少,采用copy collection
- 吞吐率高
- 老年代
- 对象趋于一直活着
- 可以采用mark-sweep collection
- 对象趋于一直活着
- 年轻代
- 分代GC
- 步骤:
- 引用计数
- 每个对象都有与之关联的引用数目
- 对象存活条件,引用数大于0
- 优点:
- 内存管理操作平摊到程序指向过程中
- 内存管理不需要了解runtime的实现细节
- 缺点:
-开销较大:通过原子操作,保证对引用计数的原子性和可见性
- 无法回收环形数据结构
- 内存开销大
- 回收内存可能引发暂停【回收大数据结果时】
- 追踪垃圾回收
- 三个任务:
- 动态内存
内存管理及优化
go内存分配
- 目标:对象在heap上分配
- 实现
- 提前将内存分块
- 调用mmap()申请大块内存 eg:4MB
- 将大内存分成大块 8KB
- 将大块分成特定大小的小块,用于对象分配
- noscan mspan 分配不包含指针 --GC不需要扫描
- scan mspan --GC需要扫描
- 提前将内存分块
- 缓存
- 一致性原理
go 内存管理优化
- 对象分配是高频操作
- 大多数是小对象
- 分配路径长:g->m->p->mcache->mspan->memory block ->return block
优化方案
- 每个 g(goroutinue) 都绑定一大块内存(1kb)
- GAB 用于noscan类型的小对象分配<128B
- 用三个指针维护GAB:base,end,top
- 与其他分配请求不互斥
- 简述
- GAB 对于go内存来说是一个大对象
- 本质:将小对象的分配合成为一个大对象分配,减少了分配次数
- 问题:GAB对象的分配方式会导致内存延迟释放
- 一个小对象存活导致GAB一直存活
- 解决方式: 当GAB的总大小超过阈值时,将GAB中存活对象复制到另外分配的GAB中
- 原本的GAB可以释放
- 用copying GC 算法管理小对象
- 一个小对象存活导致GAB一直存活