这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天,初步了解了原生go语言里对于内存管理的做法以及字节内部对于go语言内存管理的优化
内存管理的优化
go语言中是在堆上来动态分配内存的,具体做法是先向操作系统申请一大块内存然后对其进行两次划分,最终划分为特性大小的小块,用于内存分配。在内存分配中由于对于小内存的分配比较频繁,且go语言中内存分配由于需要执行的路径长,所以导致内存分配比较耗时。字节跳动内部研究了一套内存分配优化的方案:Balanced GC
在go语言中,go的调度器是GMP模型,每个g上都会绑定一大块内存,称为GAB。后面需要获取小内存时只需要直接从GAB上获取即可,相比于Go原生的内存分配方案来说缩短了很多时间,其分配动作简单高效且无须和其他分配请求互斥
GAB对于GO内存管理来说是一个大对象,其本质是将多个小对象的分配合成一个大对象的分配,但同时也带来一个问题,若一个GAB中有很小一部分的对象是存活的,那么整个GAB也会被认为是存活,导致内存会被延迟释放。解决的办法是用copying GC的算法管理小对象,即用另外一个GAB管理所有小的存活的内存对象,释放掉原来的GAB
编译器和静态分析
静态分析:不执行程序代码,但推导程序的行为来优化对应的代码
静态分析分为过程内分析和过程间分析。过程内分析即在一个函数内部进行代码的分析,而过程间则是函数之间存在调用情况,所以需要过程间分析。但过程间分析由于被调用函数的调用者难以确定,而且需要联合求解多个控制流和数据流,比较复杂,因此引出下面的编译器优化
编译器优化
函数内联
将被调用的函数的副本替换到调用位置上,同时重写代码以反映参数的绑定,这样子就能把过程间分析转换成过程内分析,但同时也体现出过程间的分析
优点:
- 消除函数调用开销,例如传递参数、保存寄存器等等
- 将过程间分析转化为过程内分析,帮助其他优化
缺点:
- 函数体变大,且对于缓存不友好
- 编译生成的Go镜像变大且编译时间变长
但不是所有场景都适用函数内联,比如递归函数,当递归层次比较大时,内联会导致最终的函数体十分大
逃逸分析
逃逸分析即分析代码中指针的动态作用域,就是指针在何处可以被访问
当满足以下场景之一则说明发生了逃逸,指针p在当前作用域s
- 作为参数传递给其他函数
- 传递给全局变量
- 传递给其他的goroutine
- 传递给已逃逸的指针指向的对象
上述的场景都表明了指针p指向的对象在当前作用域s外也能够被使用
函数内联拓展了函数边界,使更多原本是逃逸的对象不逃逸了,好处是对象在栈上分配和回收速度快,只需要移动sp即可,且减少在heap上的分配,降低了GC的负担