第四节高性能 Go 语言发行版优化与落地实践 | 青训营笔记

167 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记 image.png

讲师介绍与课程回顾介绍

image.png image.png image.png 两个方面的优化 image.png image.png 对go底层需要解决通用的性能问题
需要用数据驱动的方法来解决性能问题
首先优化最大瓶颈 image.png 主要写go SDK,右下角的图,把GoSDK分成接口方面,api和接口优化的新的API,实现部分有编译器,调度器,gc等,保证接口稳定的情况下去做
go要用更多的测试用例去覆盖场景,确定优化没有问题。
隔离性指的是,选项是否开启这个优化,如果不开启是和之前一样的,保证稳定性
开启优化需要日志输出 image.png image.png

自动内存管理

image.png

概念

image.png 程序运行根据需求分类的内存,比如malloc,自动的花就由程序运行时系统管理的动态内存 自动内存管理叫垃圾回收,连续释放两次内存,用了又去使用的问题,会导致正确性和安全性 image.png image.png image.png 内存使用方面使用垃圾回收会有效果,核心任务如上 image.png 从runtime里面看到的
看一下专业名字 image.png 进程里不同线程不一样的介绍,用户启动的线程,和用户启动的go routine都属于业务线程 parallel,并行,那就是多个 concurrent,串行的,一边gc,一遍做业务 image.png 第一张图,是gc之前没有标记到,在做标记回收的时候,一个新的对象b由y指向,但是b还没有标记,需要CGC需要把b也标记成存活,collectors必须要感受到对象指向关系 image.png

Tracing garbage collection

image.png image.png 标记灰色为活着的,把存活复制
image.png 标记清理,将死亡对象进行内存分配 image.png 压缩

generational GC (一种管理gc的方式)

image.png 注意分代假说,函数里new一堆对象,返回后里面创建的就死掉了 image.png 吞吐率指花了多少时间做GC
老年代趋与一直或者,反复复制开销较大,使用mark sweep做
需要根据对象的周期来处理

reference counting(另一种)

image.png 蓝色圈圈是引用对象,数字表示引用的计数,被清理后就都清理了 image.png o的引用计数就1 image.png o的引用计数就变成2 image.png 清0就可以被释放了 可以看到程序一遍执行,一遍在进行辣鸡管理,处理内存
同时不需要了解runtime细节,只要计数即可,一些库就可以做这种处理,例如c++智能指针 智能追踪和runtime耦合很高
image.png 原子操作比较有问题,同时环形操作如下图,不能回收 image.png image.png 如图null的时候大的数据结构也有问题

总结

image.png

Go内存管理和优化(主要是堆上的分析,对于栈要逃逸分析)

Go内存分配

image.png image.png image.png image.png image.png Go 内存管理构成了多级缓存机制,从 OS 分配得的内存被内存管理回收后,也不会立刻归还给 OS,而是在 Go runtime 内部先缓存起来,从而避免频繁向 OS 申请内存。内存分配的路线图如下。 分层使用缓存,从go routine分配,在p上有数据结构,根据对象大小找到mspans,如果mspan满了,就在mcentral找到填到mspans即可 image.png image.png 很多时候是分配小对象 image.png 需要对对象分配做优化

优化方案,balanced GC

image.png 三个指针维护GAB,可以在GAB上做指针对撞操作不需要去做之前的流程,直接返回创建大小的指针即可
对象分配进行分析,针对小对象就可以有优化思路 image.png image.png 用copy gc来管理小对象,根据对象的生命周期,就能进行清理策略 image.png Q:g1和g2是用新的gab? S:g1g2被回收了 Q:g用于小对象分配的noscan,如果用于大于的,按照原来的方法 修改指针的过程需要stop work 用额外的结构体来判断地址其实的空间,go的对象没有对象头 PPT里mcache管理着一组mspan,是每个级别的mspan都会有一个 image.png

编译器和静态分析

image.png

基本介绍

image.png 源代码是字符串的流,经过词法分析变成词素,然后语法分析器变成抽象语法树,对语义分析器生产结果变成decorated ast,用ir变成中间代码,然后优化和生成,主要学习编译器后端变化

数据流和控制流

image.png image.png image.png 根据控制流输出数据流

过程内和过程间分析

image.png image.png image.png

Go编译器优化

image.png image.png

函数内联

image.png 直接把副本复制到调用函数里面,然后直接达成过程内分析,帮助优化 image.png 通过编译器参数选项选择inline还是不inline image.png 可以看到优化的效果了 image.png 不好放到icache里面,会产生icachemiss image.png 内联一般是好的,但是如果是递归还是什么的,会导致Go镜像变大,编译器会调动策略来做调整

Beast Mode go编译器优化的成品

image.png

逃逸分析

image.png 被其他函数访问到了,那就是逃逸了,反之没有逃逸 image.png 多内联使得不逃逸,可以在栈上分配,从而降低gc负担 image.png beast mode 在栈上使用降低了内存使用率 image.png image.png 主要是对内存和编译器优化 image.png Q&A Q:mutator和go routine 关系 A:理解为一致 Q:代码循环引用监测发现 A:用weak reference抓住这种特性进行回收 Q:GO语言GC的过程 A:第一是扫描go routine过程,然后暂停,最后清理的时候也有一个暂停 Q:mspan用完会怎么样 A:假设用完了,就找mcentral的空余块填充,依次往下 A:mspan 是一样大的 A:micro -benchmark 是一个基准的程序,优化会不会有结果,用gotest做的,就能得到结果 Q:内联问题 A:编译根据策略要不要inline进来 A:做清理看mark的块置1或者0 Q:scan类型的为什么不在GAB上分配? A:如果有扫描着的对象就不好对其分配,在内存被延迟释放。用并行进行清楚解决方法??不是太理解 Q:并发清除怎么解决? A:新生成就变成黑色,做GC分配时,就不会漏掉了 Q:gmp里的g上面讲过了