这是我参与「第五届青训营」笔记创作活动的第7天
编译器优化思路~
前言:
本次课程的下半部分笔记,主要介绍了 Go 语言编译器和在编译器中经常用到的静态分析,通过对编译器基本算法的讲解,引出了编译器优化思路 ...
了解编译器
编译器(compiler)是非常重要的系统软件,用来识别符合语法和非语法的程序,并针对不同的平台生成正确且高效的代码。
简单来说编译器就是将一种语言(通常为高级语言)翻译为另一种语言(通常为低级语言)的程序,编译器的作用是把便于人编写的高级语言源代码翻译成目标语言机器代码的等价程序。
编译器的结构
分析部分(前端 front end):
- 词法分析,生成词素(lexeme)
- 语法分析,生成语法树
- 语义分析,收集类型信息,进行语义检查
- 中间代码生成,生成 intermediate representation(IR)
综合部分(后端 back end):
- 代码优化,机器无关优化,生成优化后的 IR
- 代码生成,生成目标代码
编译器 vs. 解释器
一些语言(比如 Python)不需要进行编译就能直接运行,是因为这种语言使用的是解释器。
解释器可以直接执行用编程语言编写的指令的程序,不过解释器需要一边“翻译”一边运行程序,程序运行速度上比使用编译器一次性编译好的程序要慢。
| 使用编译器的语言 | 使用解释器的语言 | |
|---|---|---|
| 如何分发程序 | 目标平台能直接运行编译好的程序 | 需要目标平台的解释器 |
| 跨平台支持 | 需要注意不同平台的差异 | 容易制作跨平台程序 |
| Debug | 调试困难 | 更容易发现错误 |
| 速度 | 编译好的程序性能更好,但编译时间可能比较长 | 程序运行速度慢 |
| 开发工具 | 依赖 IDE 来简化开发 | 使用文本编辑器也可以 |
静态分析
静态分析是做编译器优化时用到的一项非常重要的技术。
静态分析不执行程序代码,推导程序的行为,分析程序的性质。通常有两种方式:控制流的分析、数据流的分析。
- 控制流(Control flow):程序执行的流程
- 数据流(Data flow):数据在控制流上的传递
通过分析控制流和数据流,我们可以知道更多关于程序的性质,进而根据这些性质优化代码。
int c;
c = 12;
if (true) {
c = 2;
}
return c * 2;
比如上面的代码我们会发现最后一定返回数字 4,那么我们就直接把这段代码精简成 return 4 就好了。
过程内分析 & 过程间分析
- 过程内分析(Intra-procedural analysis)
- 仅在函数内部进行分析
- 过程间分析(Inter-procedural analysis)
- 考虑函数调用时参数传递和返回值的数据流和控制流
过程间分析需要同时分析控制流和数据流,通常比较复杂,后面在编译器优化的部分会介绍如何来避免过程间分析,同时又能达到过程间分析的目的。
Go 编译器优化
编译器优化的目的是用编译时间换取更高效的机器码,主要面向后端长期执行的任务,让程序获得性能上的收益。Go 的官方编译器虽然速度很快,但生成的代码还不是性能最高的,字节跳动基础架构语言团队推出了一个额外的编译模式,即 Beast mode。
Beast mode 是集成在 Go SDK 里面的用来做编译优化的产品,里面包含了很多编译优化的方式,本节课程主要介绍了其中两种:函数内联与逃逸分析。
函数内联(Inlining)
函数内联就是将调用函数的函数体(callee)的副本替换到调用位置(caller)上,同时重写代码以反映参数的绑定。
⭐优点:
- 消除函数调用开销,例如传递参数,保存寄存器等;
- 将过程间分析转化为过程内分析,帮助其他优化,例如逃逸分析;
- 函数内联在大多数情况下是正向优化。
👎缺点:
- 函数体变大,instruction cache(icache-指令缓存)不友好;
- 编译生成的 Go 镜像变大。
Go 的语言特性例如 interface、defer 等限制了函数的内联,Go 官方编译器的内联策略非常保守,因此 Beast mode 调整了函数内联的策略,使更多函数被内联。
逃逸分析
逃逸分析是指分析代码中指针的动态作用域(指针在何处可以被访问)。
🎈大致思路:
- 从对象分配处出发,沿着控制流,观察对象的数据流;
- 若发现指针
p在当前作用域s:- 作为参数传递给其他函数
- 传递给全局变量
- 传递给其他的 goroutine
- 传递给已逃逸的指针指向的对象
- 则指针
p指向的对象逃逸出s,反之则没有逃逸出s。
☕Beast mode 所做的优化:
- 函数内联拓展了函数边界,更多对象不逃逸。
- 优化:未逃逸的对象可以在栈上分配
- 对象在栈上分配和回收很快:移动
sp - 减少在 heap 上的分配,降低 GC 负担
- 对象在栈上分配和回收很快:移动
🚀【参考】🚀