编译器优化思路 | 青训营笔记

88 阅读3分钟

这是我参与「第五届青训营」笔记创作活动的第十二天。

一、本堂课的重点内容

image.png image.png

二、详细知识点如下

1、编译器的结构

  • 静态分析:不执行代码,推导程序的行为,分析程序的性质。
  • 控制流:程序的执行流程
  • 数据流:数据在控制流上的传递

上图的程序转换成控制流图 (control-flow graph)

通过分析控制流和数据流,我们可以知道更多关于程序的性质(properties) ,这些事实可以帮助我们做编译优化。

-   例如下面的程序。我们通过分析数据流和控制流,知道这个程序始终返回 4。编译器可以根据这个结果做出优化。

  • Intra-procedural analysis: 函数内分析:在函数内进行控制流和数据流的分析。
  • Inter-procedural analysis: 函数间分析:除了函数内的分析,还需要考虑跨函数的数据流和控制流,例如参数传递,函数返回值等。

2、Go 编译器优化

1)目的

  • 用户无感知,重新编译即可获得性能收益
  • 通用的优化手段

2) 现状

  • 采用的优化较少
  • 追求编译时间短,因此没有进行复杂的代码分析和优化

3)思路

  • 面向后端长期执行的任务
  • 用适当增加编译时间换取更高性能的代码

4) 函数内联

  • 定义:将被调用函数的函数体的副本替换到调用位置上,同时重写代码以反映参数的绑定
  • 优点

    • 消除调用开销
    • 将过程间分析的问题转换为过程内分析,帮助其他分析,例如逃逸分析

image.png image.png

使用micro-benchmark快速验证和对比性能优化结果。

  • 缺点

    • 函数体变大
    • 编译生成的 Go 镜像文件变大

函数内联在大多数情况下是正向优化,即多内联,会提升性能。

5)Beast Mode

  • Go 内联的限制

    • 语言特性:interface, defer 等等,限制了内联优化
    • 内联策略非常保守
  • 字节跳动的优化方案

    • 修改了内联策略,让更多函数被内联
    • 增加了其他优化的机会:逃逸分析
  • 开销

    • Go 镜像大小略有增加
    • 编译时间增加
    • 运行时栈扩展开销增加

6)逃逸分析

  • 定义:分析代码中指针的动态作用域,即指针在何处可以被访问
  • 大致思路

    从对象分配处出发,沿着控制流,观察数据流。若发现指针 p 在当前作用域 s:

    -   作为参数传递给其他函数;
    -   传递给全局变量;
    -   传递给其他的 goroutine;
    -   传递给已逃逸的指针指向的对象;
    
    • 则指针 p 逃逸出 s,反之则没有逃逸出 s.

函数内联拓展了函数边界,更多对象不逃逸

优化:未逃逸出当前函数的指针指向的对象可以在栈上分配

对象在栈上分配和回收很快:移动 sp 即可完成内存的分配和回收;
 
减少在堆上分配对象,降低 GC 负担。

三、课后个人总结

编译器 Beast mode 拥有更多的优化手段,执行效率更高。在开发阶段使用标准编译模式,提高开发效率;发布到线上时使用 Beast mode 编译生成性能更高的二进制。

image.png 这个编译器优化效果比较明显,在很多 Benchmark 上都取得了比较好的效果,如下图所示,time/op 越少越好. image.png

四、引用参考

www.163.com/dy/article/…