持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情
Lesson 4高性能GO语言发行版优化与落地实践
00相关背景
性能优化:提升软件系统处理能力,减少不必要的消耗,充分发挥计算机算力
下图为软件的基本结构
SDK和基础库代码为我们提供了抽象的逻辑,如网络库和IO库
语言运行时:语言的实现GC 调度器
OS:提供隔离的运行环境
SDK是更基础的应用,具有通用性
利用pprof产生的数据进行性能分析
落地实现是GO SDK的代码优化
在保证接口稳定时:已有的接口已有用户依赖
测试用例:以测试驱动开发
文档:优化的内容
隔离:不开启优化的用户不打扰,保证软件的稳定性
可观测:记录数据
01自动内存管理
1.1动态内存管理-相关概念以及几种方式
****
Double-free problem:连续两次释放了一块内存
Use-after-freeproblem释放之后又使用这块内存
内存使用不当时会带来正确性和安全性的问题
(GC的)三个任务
两个概念:
Cocurrently:并发
Mutator threads: 突变体线程
对象的"死去":这里的死去即不可能再被任何途径使用的对象 即不可达
GC:Garbage Collection垃圾回收,将死去的对象回收
GC在堆上执行
业务线程:用户启动的线程
1. 先是4个mutator执行,当需要执行GC时,一个Pause对内存进行标记以及回收,GC执行完毕后,再继续mutator的执行 serialGC 有暂停(STW:stop the word),只有一个collector
2. 与1类似,但比1更高效 因为在pause阶段有4个collector
3. 一边回收 一边执行用户线程 GC执行之后就将collector休眠
图解:
2:Concurrent GC
3:已经被标记的对象O指向的对象B(也为存活状态)也要标记 否则就会漏掉 GC会出错
(三色标记 混合写屏障)
希望花在GC上的时间越少越好 它是额外的时间 使程序运行更快
GC的内存开销越小越少
1.2垃圾追踪回收
由根对象向下由引用关系找可达对象
(JAVA中的概念)
根对象:映射的最高一层称为根对象 其下有引用链上的对象
可达:在GC Roots存在的引用链上存在引用关系 可达 在图上标示为有路径
标记的对象是存活的对象
三种垃圾回收方式:
1. Copying GC:把左边当前存活的对象复制到另外一个内存,再把原本所在的内存清空
2. Mark-sweep GC:(标记清理GC)把死亡对象的内存空间用free list管理,先标记死亡对象,然后将其清理
3.Compact GC:(标记压缩)只有一块内存空间,将存活的对象复制到这一块内存最前面,然后把剩余的内存清理掉
与Copying 的区别:是原地整理对象 不用另辟空间
1.3分代GC
分代假说:new创建的大多数对象 在所在函数return之后,就死掉了
年轻代,老年代
很多年轻代很快就死掉了,copy开销小,吞吐率大,时间少
老年代趋于一直活着,保持待着不变
分代GC体现根据对象的不同生命周期来制定不同的GC策略,从而降低整体内存管理的开销
1.4引用计数
引用数目:图中表示为被箭头指向的次数
把指针指向null:引用计数会清零,之后该对象就可以被清空
C++的智能指针是引用计数的一个例子
基于追踪的垃圾回收会与runtime耦合得更紧密一点,而引用计数是由一个专门的库提供
缺点:引用的对象比较多,所以引用计数的开销较大
环上的对象均不可达,但由于其引用计数均为1,故引用计数无法清理该内存
这一个地方置为null时,可以挨个对这一串上的对象回收
数据结构较大时,仍容易引发暂停
02.GO内存管理及优化
GO内存分配的主要思想:分块 缓存
2.1分块和缓存
缓存:
用mspon进行对象分配
GO的缓存借鉴TCMalloc
GC的扫描:包含指针的对象,需要追踪指针指向的所有对象,并将其一并标记,称为扫描
分配内存:go rutine上的代码开始分配
Mcache中存不同大小的mspon 根据对象的大小找到一个合适的mspon
若mcache中的mspon均满了,则需要从下一级mcentral中找到空余对象的mspon将其填入mcache
2.2GO内存管理优化
大多时候分配的都是小对象
2.3
字节对于小对象在GAB上进行独立的指针碰撞进行对象分配而不影响其他
将g1,g2中存活对象copy到一个新的GAB,再把原来的清理掉
根据对象的生命周期,使用不同的标记清理策略
集群和分布式:
小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群
03编译器和静态分析
3.1编译器的结构
****
IR与机器无关,可用于不同编译器
3.2静态分析
编译器后端优化的一个方法是静态分析
控制流和数据流是静态分析的两种主要方式
控制流:针对程序的流程
数据流:用控制流来推数据在流程中的变化
3.3过程内分析和过程间分析
04GO编译器优化
4.1函数内联
****
内联可以提高性能
函数内联:把函数体复制一份到调用位置,体现参数的传递
把两个函数合并成一个函数,故不需要再函数调用,过程间分析
4.2逃逸分析
Micro-benchmark用数据看看优化效果
镜像:镜像(Mirroring)是一种文件存储形式,是冗余的一种类型,一个磁盘上的数据在另一个磁盘上存在一个完全相同的副本即为镜像。
内联不能无休止 否则规模会太大
逃逸:指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量引用。正常的方法调用中,方法体中创建的对象将在执行完毕之后,将回收其中创建的对象;故由于无法回收,即成为逃逸。
一些函数无法内联
全局变量谁都可以访问 故逃逸
传到其他goroutine,可以被其他*访问,故也逃逸
答疑:
用户启动的一个go routine就是一个mutator
代码的循环使用可以用引用计数来发现
GO语言的GC仍有STW(暂停)过程 但很短
一个mspon里的所有对象的内存大小是一样大的
函数是否inline由编译器决定