编译器优化 | 青训营笔记

94 阅读3分钟

编译器优化 | 青训营笔记

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

编译器和静态分析

编译器的存在意义是识别符合语法和非法的程序,生成正确且高效的代码

编译器的基本结构:

  • 分析部分(前端)

    • 词法分析,生成词素
    • 语法分析,生成语法树
    • 语义分析,收集类型信息,进行语义检查
    • 中间代码生成,生成intermediate representation(IR)
  • 综合部分(后端)

    • 代码优化,机器无关优化,生成优化后的IR
    • 代码生成,生成目标代码

静态分析

不执行程序代码,推导程序的行为,分析程序的性质

  • 控制流:分析程序执行的流程
  • 数据流:分析数据在控制流上的传递

通过分析控制流和数据流,可以知道更多关于程序的性质,然后根据这些性质优化代码。

比如

 if (true) {
     return 2;
 }
 //优化后
 return 2;

过程内分析和过程间分析

过程内分析:仅在函数内部进行分析

过程间分析:考虑过程调用时参数传递和返回值的数据流和控制流

通常情况下,过程间分析比过程内分析更为复杂,要尽可能避免过程间分析的情况。

Go编译器优化

  • 编译器优化的好处

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

    • 场景:面向后端长期执行任务
    • 用编译时间换取更高效的机器码
  • Beast mode

    • 函数内联
    • 逃逸分析
    • 默认栈大小调整
    • 边界检查消除
    • 循环展开
    • 。。。

函数内联

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

  • 消除函数调用开销,例如传递参数、保存寄存器等
  • 过程间分析转化为过程内分析,帮助其他运行,例如逃逸分析
  • 函数体变大,instruction cache不友好
  • 编译生成的Go镜像变大

函数内联策略在大多数情况下都是正向优化。

根据不同的情况会有不同的内联策略,比如根据调用者函数和被调用者函数的规模。

Beast Mode

Go函数内联受到的限制较多

  • 语言特性
  • 内联策略十分保守

Beast mode调整了函数内联的策略,使得更多函数被内联

  • 降低函数调用的开销
  • 增加了其他优化的机会,如逃逸分析

相应的开销也会增大

  • Go镜像增加大约10%
  • 编译时间增加

逃逸分析

听下来我感觉就是判断指针的密封性,即其他外部代码能否访问该指针。

逃逸分析即分析代码中指针的动态作用域,判断指针在何处可以被访问

大致思路:

  • 从对象分配处出发,沿着控制流,观察对象的数据流

  • 若发现指针p在当前作用域s:

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

通过Beast mode,函数内联扩展了函数边界,原先逃逸的对象在这之后就可能不逃逸了。也就是更多对象不逃逸。

这些未逃逸的对象可以在栈上分配

好处:

  • 对象在栈上分配和回收很快:移动sp
  • 减少在heap上的分配,降低GC负担