这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
课程内容与选题缘由
上一届课讲到了Go内存分配和编译器的优化。今天就总结一下编译器相关的课程笔记。
编译器的结构
编译器是重要的系统软件,他的作用是:
- 识别符合的语法和非法的程序
- 生成正确且高效的代码
编译器中的分析器:
- 词法分析:生成词素(lexeme)
- 语法分析:生成语法树
- 语义分析:收集类型信息,进行语义检查
- 中间代码生成:生成intermediate representation(IR)
综合部分:
- 代码优化:机器无关代码,生成优化后的IR
- 代码生成:生成目标代码
编译器优化的方法
静态分析
静态代码分析是编译器优化的一部分。
静态分析的过程中,不执行程序代码,之推导程序的行为,分析程序的性质。
它可以在程序编译时分析出程序的控制流和数据流,从而对程序的性能进行优化。
使用静态代码分析可以提高代码的执行效率,降低程序的内存占用,并减少编译器在运行时所需要的资源。
静态分析的分类:
- 控制流分析:程序执行的流程(提前知道要走哪条路径)
- 数据流:数据在控制流上的传递(不用编译计算过程,直接返回结果值)
•例如上面的程序。我们通过分析数据流和控制流,知道这个程序始终返回 4。编译器可以根据这个结果做出优化。
过程内分析、过程间分析
过程内分析:仅在函数内部进行分析
过程间分析:考虑过程调用时参数传递和返回值的数据流和控制流
过程间分析的难点:
- 需要通过数据流分析得知
i的具体类型,才能知道i.foo()调用的是哪个foo() - 根据
i的具体类型,产生了新的控制流,A.foo(),分析继续 - 过程间分析需要同时分析控制流和数据流——联合求解,比较复杂
Go编译器优化
Go编译器的使用场景是:面向后端长期执行任务
因此可以牺牲一定编译时间换取更高效的机械码
函数内联
函数内联的定义是,将被调用函数的函数体的副本替换到调用位置上,同时重写代码以反映参数的绑定。
相当于两个函数合并成一个函数了,节省了传参和保存存储器的过程
-
优点
- 消除调用开销
- 将过程间分析的问题转换为过程内分析,帮助其他分析
-
缺点
- 函数体变大
- 编译生成的 Go 镜像文件变大
-
函数内联在大多数情况下是正向优化,即多内联,会提升性能
逃逸分析
逃逸分析可以确定一个对象是否仅在函数内使用,从而决定是否对该对象所在的函数进行内联,从而减少分配和垃圾回收的开销。逃逸分析使 Go 编译器能够确定哪些对象需要在 GC 时回收。
如果对象逃逸了,则该对象将在堆上分配;如果未逃逸,则对象将在栈上分配。
为什么未逃逸的对象在栈上分配?:
- 对象在栈上分配和回收很块:移动sp
- 减少在heap上的分匹配,降低GC带来的性能开销