Go语言开发——编译器优化 | 青训营笔记

122 阅读3分钟

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

课程内容与选题缘由

上一届课讲到了Go内存分配和编译器的优化。今天就总结一下编译器相关的课程笔记。

编译器的结构

1

2

编译器是重要的系统软件,他的作用是:

  • 识别符合的语法和非法的程序
  • 生成正确且高效的代码

编译器中的分析器:

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

综合部分:

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

编译器优化的方法

静态分析

静态代码分析是编译器优化的一部分。

静态分析的过程中,不执行程序代码,之推导程序的行为,分析程序的性质。

它可以在程序编译时分析出程序的控制流和数据流,从而对程序的性能进行优化。

使用静态代码分析可以提高代码的执行效率,降低程序的内存占用,并减少编译器在运行时所需要的资源。

静态分析的分类:

  • 控制流分析:程序执行的流程(提前知道要走哪条路径
  • 数据流:数据在控制流上的传递(不用编译计算过程,直接返回结果值

3

4

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

过程内分析、过程间分析

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

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

过程间分析的难点:

  • 需要通过数据流分析得知i的具体类型,才能知道i.foo()调用的是哪个foo()
  • 根据i的具体类型,产生了新的控制流A.foo(),分析继续
  • 过程间分析需要同时分析控制流和数据流——联合求解,比较复杂

Go编译器优化

Go编译器的使用场景是:面向后端长期执行任务

因此可以牺牲一定编译时间换取更高效的机械码

函数内联

5

6

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

相当于两个函数合并成一个函数了,节省了传参和保存存储器的过程

  • 优点

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

    • 函数体变大
    • 编译生成的 Go 镜像文件变大
  • 函数内联在大多数情况下是正向优化,即多内联,会提升性能

逃逸分析

7

逃逸分析可以确定一个对象是否仅在函数内使用,从而决定是否对该对象所在的函数进行内联,从而减少分配和垃圾回收的开销。逃逸分析使 Go 编译器能够确定哪些对象需要在 GC 时回收。

如果对象逃逸了,则该对象将在堆上分配;如果未逃逸,则对象将在栈上分配。

为什么未逃逸的对象在栈上分配?:

  • 对象在栈上分配和回收很块:移动sp
  • 减少在heap上的分匹配,降低GC带来的性能开销