这是我参与「第五届青训营」伴学笔记创作活动的第5天
一、GO内存支配——分块
目标:为对象在heap.上分配
内存
提前将内存分块
1.调用系统调用mmapQ)向OS申请大块内存,例如4 MB
2.先将内存划分成大块,例如8 KB,称作mspan
3.再将大块继续划分成特定大小的小块,用于对象分配
4.noscan mspan:分配不包含指针的对象一GC 不需要扫描
5.scan mspan:分配包含指针的对象—GC需要扫描
对象分配:根据对象的大小,选择最合适的块返回
二、 Go内存分配-缓存
1.TCMalloc thread caching
2.每个p包含一个mcache用于快速分配,用于为绑定于p上的g分配对象
3.mcache管理一组mspan
4.当mcache中的mspan分配完毕,向mcentral申请带有末分配块的mspan
5.当mspan中没有分配的对象,mspan 会被缓存在mcentral中,而不是立刻释放并归还给OS
GO 内存管理优化
1.对象分配是非常高频的操作:每秒分配GB级别的内存
2.小对象占比较高
3.Go内存分配比较耗时
(1)分配路径长:g->m->p->mcache->mspan->memory block->return pointer(分配路径很长,非常消耗CPU)
(2)pprof:对象分配的函数是最频繁调用的函数之一
优化方案:Balanced GC
1.每个g都给绑定一大块内存(1KB),称作 goroutine allocation buffer (GAB)
2.GAB用于noscan类型的小对象分配:<128B
3.使用三个指针维护GAB:base,end,top
4.Bump pointer(指针碰撞)风格对象分配
(1)无须和其他分配请求互斥
(2)分配动作简单高效
if top + size <=end{
adder:=top
top+=size
return adder
}
GAB对于Go内存管理来说是一个大对象
本质:将多个小对象的分配合并成一次大对象的分配
问题:GAB的对象分配方式会导致内存被延迟释放
方案:移动GAB中存活的对象
1.当GAB总大小超过一定阈值时,将GAB中存活的对象复制到另外分配的GAB中
2.原先的GAB可以释放,避免内存泄漏
3.本质:用copying GC的算法管理小对象
Balanced GC——性能收益
高峰期CPU usage降低4.6%,核心接口时延下降4.5%—7.7%
总结
1.Go内存管理—分块
2.Go内存管理—缓存
3.Go对象分配的性能问题
(1)分配路径过长
(2)小对象居多
4.Balanced GC
(1)指针碰撞风格的对象分配
(2)实现了copying GC
(3)性能收益
三、编译器和静态分析
编译器的结构
1.重要的系统软件
(1)识别符合语法和非法的程序
(2)生成正确且高效的代码
2.分析部分(前端 front end)
(1)词法分析,生成词素(lexeme)
(2)语法分析,生成语法树
(3)语义分析,收集类型信息,进行语义检查
(4)中间代码生成,生成 intermediate representation(IR)
3.综合部分(后端 back end)
(1)代码生成,机器无关优化,生成优化后的IR
(2)代码生成,生成目标代码
静态分析
1.静态分析:不执行程序代码,推导程序行为,分析程序性质
- 控制流(Control flow):程序执行的流程
3.数据流(Data flow):数据在控制流上的传递
4.通过夫人逆袭控制流和数据流,我们可以知道更多关于程序的性质(properties)
5.根据这些性质优化代码
过程类和过程间的分析
1.过程内分析(Intra-procedural analysis)
(1)仅在过程内部进行分析
2.过程间分析(Inter-procedual analysis)
(1)考虑过程调用时参数传递和返回值的数据流和控制流
3.为什么过程间分析是个问题
(1)需要通过数据流分析得知 i 的具体类型,才能知道 i,foo() 调用的是哪个foo()
(2)根据 i 的具体类型,产生了新的控制流,i foo(),分析继续
(3)过程间分析需要同时分析控制流和数据流——联合求解,比较复杂
四、Go编译器优化
1.为什么做编译器优化
(1)用户无感知,重新编译即可获得性能收益
(2)通用性优化
2.现状
(1)采用的优化少
(2)编译时间较短,没有进行较复杂的代码分析和优化
3.编译优化的思路
(1).场景:面向后端长期执行任务
(2)Tradeoft:用编译时间换取更高效的机器码
4.Beast mode
(1)函数内联
(2)逃逸分析
(3)默认栈大小调整
(4)边界检查消除
(5)循环展开 等等
函数内敛(Inlining)
1.内联:
将被调用函数的函数体(callee)的副本替换到调用位置(caller)上,同时重写代码以反映参数的绑定
2.优点
(1)消除函数调用开销,例如传递参数、保存奇存器等
(2)将过程间分析转化为过程内分析,帮助其他优化,例如逃逸分析
3.缺点
(1)函数体变大,instruction cache (icache)不友好
(2)编译生成的Go镜像变大
4.函数内联能多大程度影响性能? ——使用 micro-benchmark验证一下
Inlining优化后函数被内联后,性能数据提升 4.58倍
5.内联策略
调用和被调用函数的规模
Beast Mode
1.Go函数内联受到的限制较多
(1)语言特性,例如interface, defer等,限制了函数内联
(2)内联策略非常保守
2.Beast mode:调整函数内联的策略,使更多函数被内联
(1)降低函数调用的开销
(2)增加了其他优化的机会:逃逸分析
3.开销
(1)Go镜像增加~10%
(2)编译时间增加
逃逸分析
1.逃逸分析: 分析代码中指针的动态作用域:指针在何处可以被访问
2.大致思路
(1)从对象分配处出发, 沿着控制流,观察对象的数据流
(2)若发现指针p在当前作用域S:
(2.1)参数传递给其他函数
(2.2)传递给全局变量
(2.3)传递给其他的goroutine
(2.4)传递给已逃逸的指针指向的对象
(3)则指针p指向的对象逃逸出S,反之则没有逃逸出S
3.Beast Mode:函数内联拓展了函数边界,更多对象不逃逸
4.优化:未逃逸的对象可以在栈上分配
(1)对象在栈上分配和回收很快:移动sp
(2)减少在heap上的分配,降低GC负担
总结
1.Go编译器优化问题
2.Beast Mode
3.函数内联
4.逃逸分析
5.通过 micro—benchmark
6.性能收益
五、课程总结
1.本节课程:高性能Go原因呢发行版优化与落地实践
2.性能优化
(1)自动内存管理
(2)Go内存管理
(3)编译器与静态f分析
(4)编译器优化
3.实践
(1)Balanced GC 优化对象分配
(2)Beast mode 提升代码性能
4.分析问题的方法与解决问题的思路,不仅使用于Go语言,其他语言的优化也同样适用