编译器结构及后端优化 | 青训营笔记

83 阅读3分钟

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

编译器结构

  • 结构
    • 分析部分 (前端 front end)·
      • 词法分析,生成词素 (lexeme)
      • 语法分析,生成语法树
      • 语义分析,收集类型信息,进行语义检查
      • 中间代码生成,生成 intermediate representation (IR)
    • 综合部分 (后端 back end)
      • 代码优化,机器无关优化,生成优化后的 IR
      • 代码生成,生成目标代码

静态分析

  • 静态分析: 不执行程序代码,推导程序的行为,分析程序的性质。
  • 控制流 (Control flow): 程序执行的流程
  • 数据流 (Data flow): 数据在控制流上的传递
  • 通过分析控制流和数据流,我们可以知道更多关于程序的性质 (properties)
  • 根据这些性质优化代码

过程间分析和过程内分析

  • 过程内分析 (Intra-procedural analysis)
    • 仅在函数内部进行分析
  • 过程间分析 (lnter-procedural analysis)(比较复杂)
    • 考虑函数调用时参数传递和返回值的数据流和控制流
    • 例子
      • 分析
        • 需要通过数据流分析得知变量的具体类型,才能知道变量调用的是哪个类型的函数
        • 根据变量的具体类型,产生了新的控制流,变量的函数流,分析继续
    • 过程间分析需要同时分析控制流和数据流——联合求解,比较复杂

Go 编译器优化

  • 为什么做编译器优化
    • 用户无感知,重新编译即可获得性能收益
    • 通用性优化
  • 现状
    • 采用的优化少
    • 编译时间较短,没有进行较复杂的的代码分析和优化
  • 编译优化的思路
    • 场景: 面向后端长期执行任务
    • Tradeoff: 用编译时问换取更高效的机器码
  • Beast mode
    • 函数内联

      • 内联:
        • 将调用位置的代码替换为调用函数的函数体
        • 同时对函数体进行重写来绑定参数
      • 优点
        • 消除函数调用开销, 例如传递参数、保存寄存器等
        • 将过程间分析转化为过程内分析,帮助其他优化,例收逃飞鱼分析
      • 缺点
        • 函数体变大, instruction cache (icache) 不友好
        • 开销
          • 编译生成的 Go 镜像变大~10%
          • 编译时间增加
        • Go 函数内联受到的限制较多
          • 语言特性限制了函数内联
          • 内联策略较为保守
    • 逃逸分析

      • 分析代码中指针的动态作用域,即指针在何处可以被访问
      • 执行思路
        • 从对象分配处出发,沿着控制流,观察对象的数据流
        • 若发现指针 p 在当前作用域 s
          • 作为参数传递给其他函数
          • 传递给全局变量
          • 传递给其他的 goroutine
          • 传递给已逃逸的指针指向的对象
        • 则指针 p 指向的对象逃逸出 s,反之则没有逃逸出 s
      • 优化:未逃逸的对象可以在栈上分配
        • 对象在栈上分配和回收很快: 移动 sp
        • 减少在 heap 上的分配,降低 GC 负担
    • 默认栈大小调整

    • 边界检查消除

    • 循环展开

    • ...