1. 编译器基础回顾

各位同学好,我是这门课的主讲。今天咱们聊聊编译器的基础。

说实话,很多做AI推理的同学,一开始都觉得编译器跟自己没啥关系。我当年也是这么想的——直到我在项目中亲手踩过一个坑:手写了一个算子,性能死活上不去,后来才发现是编译器后端没处理好。嗯,从那以后,我再也不敢小看编译器了。

这一节,我们快速回顾一下编译器是怎么把源代码变成机器码的。重点看三个阶段的职责划分:前端、中端、后端。

1.1 编译器的整体流程

先看一张图,这是我手画的编译器工作流程。

编译器工作流程:从源代码到机器码 编译器前端 词法分析 语法分析 语义分析 AST 编译器中端 IR 生成 IR 优化 平台无关优化 优化后IR 编译器后端 指令选择 寄存器分配 指令调度 源代码 机器码

你看,整个流程分三段。每一段干的事完全不同。我习惯把编译器想象成一个翻译团队:前端是「语言专家」,中端是「逻辑优化师」,后端是「本地化专员」。

1.2 编译器前端:读懂你的代码

前端负责把源代码变成一种叫AST(抽象语法树)的东西。说白了,就是让计算机「理解」你写了啥。

前端内部又分三步走:

  • 词法分析:把代码拆成一个个token。比如 int a = 1 + 2; 会被拆成 int、a、=、1、+、2、;。
  • 语法分析:检查这些token的组合是否符合语法规则。比如 1 + 2 合法,但 + 1 2 就不行。
  • 语义分析:检查类型、作用域等更深层的问题。比如 float a = "hello" 类型不匹配,这里就会报错。
💡 重要概念: 前端输出的AST是树形结构,每个节点代表一个语法结构。比如一个加法表达式 1 + 2,根节点是加法操作,左子节点是1,右子节点是2。

我记得有一次,团队里有人写了个很复杂的宏展开,前端解析后AST膨胀了10倍。嗯,这就是为什么有些代码看着简单,编译起来却慢得要命。

1.3 编译器中端:真正的优化战场

中端是编译器最核心的部分。它把AST转成一种叫IR(中间表示)的东西,然后做各种优化。

为什么要有IR?你想想看,如果每种语言都直接生成机器码,那编译器得为每种语言+每种硬件组合写一套代码。有了IR,前端只管生成IR,后端只管消费IR,中间做优化也只需要写一套。

常见的IR优化包括:

优化名称 作用 举例
常量折叠 编译时计算常量表达式 a = 3 + 5 → a = 8
死代码消除 删除永远不会执行的代码 删除 if(false){...}
循环展开 减少循环开销 把循环体复制多份
公共子表达式消除 避免重复计算 a = b*c; d = b*c → 只算一次
🔧 实战技巧: 在AI推理引擎中,中端优化尤其重要。比如算子融合(把多个小算子合并成一个),就是在IR层面做的。我曾经在优化一个Transformer模型时,靠算子融合把推理速度提升了3倍。

这里有个代码示例,展示IR优化前后的对比:


// 原始代码
int x = 10;
int y = 20;
int z = x + y;
int w = x + y;  // 重复计算
if (false) {
    z = 100;     // 死代码
}

// 优化后的IR(伪代码)
x = 10
y = 20
t1 = x + y       // 公共子表达式消除,只算一次
z = t1
w = t1
// if(false) 被删除

1.4 编译器后端:生成目标机器码

后端负责把优化后的IR转成目标平台的机器码。这里要考虑具体的硬件特性。

后端主要做三件事:

  1. 指令选择:把IR操作映射到具体的CPU指令。比如加法操作,x86上用 ADD,ARM上用 ADD,但具体编码不同。
  2. 寄存器分配:决定哪些变量放在寄存器里,哪些放在内存里。寄存器快但少,内存慢但多,这是个权衡。
  3. 指令调度:重新排列指令顺序,让CPU流水线更顺畅。比如把内存加载指令提前,避免CPU空等。
⚠️ 避坑指南: 我曾经在适配一个新硬件时,发现后端生成的代码性能极差。查了半天,原来是寄存器分配策略没适配好——这个硬件只有8个通用寄存器,但后端默认用了16个的分配策略。结果大量变量被 spill 到内存,性能直接腰斩。

后端还有一个重要任务:处理ABI(应用程序二进制接口)。说白了,就是函数怎么调用、参数怎么传、返回值怎么拿。不同操作系统、不同硬件,ABI都不一样。

1.5 三端职责总结

最后,我用一个表格总结一下三端的核心职责:

阶段 输入 输出 核心关注点
前端 源代码 AST / IR 语法正确性、类型安全
中端 IR 优化后IR 性能优化、平台无关
后端 优化后IR 机器码 硬件适配、指令效率

好了,这一节就到这里。编译器基础回顾完了,后面我们就要进入正题——推理引擎的编译器适配。你会发现,很多推理引擎的优化技巧,本质上就是在编译器三端的框架下做文章。