1.编译器和静态分析
编译器的结构
静态分析:不执行代码,推导程序的行为,分析程序的性质。
控制流:程序的执行流程
数据流:数据在控制流上的传递
上图的程序转换成控制流图 (control-flow graph)
通过分析控制流和数据流,我们可以知道更多关于程序的性质(properties) ,这些事实可以帮助我们做编译优化。
例如上面的程序。我们通过分析数据流和控制流,知道这个程序始终返回 4。编译器可以根据这个结果做出优化。
Intra-procedural analysis: 函数内分析:在函数内进行控制流和数据流的分析
Inter-procedural analysis: 函数间分析:除了函数内的分析,还需要考虑跨函数的数据流和控制流,例如参数传递,函数返回值等
2.Go 编译器优化
目的
用户无感知,重新编译即可获得性能收益
通用的优化手段
现状
采用的优化较少
追求编译时间短,因此没有进行复杂的代码分析和优化
思路
面向后端长期执行的任务
用适当增加编译时间换取更高性能的代码
函数内联
定义:
将被调用函数的函数体的副本替换到调用位置上,同时重写代码以反映参数的绑定
优点:
消除调用开销
将过程间分析的问题转换为过程内分析,帮助其他分析
缺点:
函数体变大
编译生成的 Go 镜像文件变大
函数内联在大多数情况下是正向优化,即多内联,会提升性能
采取一定的策略决定是否内联
调用和被调用函数的规模
Go 内联的限制
语言特性:interface, defer 等等,限制了内联优化
内联策略非常保守
字节跳动的优化方案
修改了内联策略,让更多函数被内联
增加了其他优化的机会:逃逸分析
开销
Go 镜像大小略有增加
编译时间增加
运行时栈扩展开销增加
逃逸分析
定义:分析代码中指针的动态作用域,即指针在何处可以被访问
大致思路:
从对象分配处出发,沿着控制流,观察数据流。若发现指针 p 在当前作用域 s:
作为参数传递给其他函数;
传递给全局变量;
传递给其他的 goroutine;
传递给已逃逸的指针指向的对象;
优化:未逃逸出当前函数的指针指向的对象可以在栈上分配
对象在栈上分配和回收很快:移动 sp 即可完成内存的分配和回收;
减少在堆上分配对象,降低 GC 负担。