这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
重点内容
- 自动内存管理
- Go内存管理及优化
- 编译器和静态分析
- Go编译器优化
详细知识点
自动内存管理
· 动态内存
程序在运行时根据需求动态分配的内存:malloc()
· 自动内存管理
避免手动内存管理,专注于实现业务逻辑
保证内存使用的**正确**性和**安全性**:double-free problem、use-after-free problem
· 三个任务
为新对象分配空间 找到存活的对象 回收死亡对象的内存空间
相关概念:
· Mutator:业务线程,分配新对象,修改对象指向关系
· Collector:GC线程,找到存活对象,回收死亡对象的内存空间
· Serial GC:只有一个collector
· Parallel GC:支持多个collectors同时回收的GC算法
· Concurrent GC:mutator(s)和collector(s)可以同时执行
三种GC,Concurrent GC必须感知对象指向关系的变化,否则会导致存活对象被回收
评价GC算法:
· 安全性(Safety):不能回收存活的对象 基本要求
· 吞吐率(Throughput):1- GC时间/程序执行总时间 花在GC上的时间越短越好
· 暂停时间(Pause time):stop the world (STW) 业务是否感知
· 内存开销(Space overhead) GC元数据开销
追踪垃圾回收
回收目标:指针指向关系不可达的对象
过程:
· 标记根对象(静态变量、全局变量、常量、线程栈等)
· 标记:找到可达对象(从根对象出发,找到所有可达对象)
· 清理:所有不可达对象
· Copying GC:将存活对象复制到另外的内存空间,原空间就可分配了
· Mark-sweap GC:将死亡对象的内存标记为“可分配”
· Mark-compact GC:移动并整理存活对象,也就是让他们的地址连续,分配其他空间时就可以从后面开始分配了
分代GC
简单来说, 经历过GC的次数决定对象的年龄,年轻代的存货少,用copying gc,老年代的对象趋于一直活着,反复复制开销大,采用mark-sweep
引用计数
看到这个就联想到C++的智能指针,原理一样
· 每个对象有一个与之关联的引用数目 · 对象存活条件:当且仅当引用数大于0
缺点:
· 维护引用计数开销大:通过原子操作保证对引用计数操作的原子性和可见性 · 无法回收环形数据结构 -- weak reference · 内存开销 · 回收内存时依然可能引发暂停
Go内存管理及优化
分块
目标:为对象在heap上分配内存
提前将内存分块
系统调用mmap()向OS申请一大块内存,例如4MB 先将内存划分为大块,例如8KB,称为mspan 再将大块继续划分为特定大小的小块,用于对象分配 noscan mspan:分配不包含指针的对象--GC不需要扫描 scan mspan:分配包含指针的对象--GC需要扫描
缓存
虚拟内存一类的?这里就讲了个大概
内存管理优化
Balanced GC
本质:将多个小对象的分配合并成一次大对象的分配
方案:移动GAB中存活的对象 当GAB总大小超过一定阈值时,将GAB中存活的对象复制到另外分配的GAB中 原先的GAB可以释放,避免内存泄漏 本质:用copying GC的算法管理小对象
编译器和静态分析
结构
静态分析
不执行程序代码,推导程序行为,分析程序的性质
两种分析
控制流(Control flow): 程序执行的流程 数据流(Data flow): 数据在控制流上的传递
过程内分析和过程间分析
过程内(Intra-procedural analysis):仅在函数内进行分析 过程间(Inter-procedural analysis):考虑过程调用时参数传递和返回值的数据流和控制流
过程间分析要得知调用接口的时哪个类型的变量
Go编译器优化
WHY: 用户无感知,重新编译即可获得性能收益 通用性优化
思路: 用编译时间换取更高效的机器码
函数内联
内联:将被调用函数的函数体的副本替换到调用位置上,同时重写代码以反映参数的绑定
优点: 消除调用开销 将过程间分析转化为过程内分析
缺点: 函数体变大,icache不友好 编译生成的Go镜像变大
Beast mode
Go受到很多限制:interface,defer等,限制了内联。 内联策略保守
Beast mode:调整内联策略,使更多函数被内联 降低函数调用的开销 增加其他优化的机会
逃逸分析
分析代码中指针的动态作用域:指针在何处可以被访问
若发现指针p在当前作用域s: · 作为参数传给其他函数 · 传递给全局变量 · 传递给其他goroutine · 传递给已经逃逸的指针指向的对象 则p指向的对象逃逸出s,反之则没有逃逸出s
Beast mode 让更多函数被内联,扩展了函数边界,更多对象不逃逸
优化:未逃逸的对象可在栈上分配 对象在栈上的分配和回收很快,只要移动sp指针就行 减少在heap上的分配,降低GC负担