本文档涵盖了 Go 编译器中的静态单赋值 (SSA) 优化过程。这些优化在 SSA 中间表示上进行操作,以提高代码效率、消除冗余操作以及执行特定于体系结构的转换。有关通用 SSA 表示及其在编译流程中的作用的信息,请参阅 3.2。
Go 编译器在从 Go 源代码构建函数后,会对函数的 SSA 形式应用各种优化过程。这些优化在保持程序语义的同时,转换代码以提高运行时性能。优化过程包括适用于所有目标体系结构的通用优化以及特定于体系结构的转换。
主要优化类别包括:
来源
值证明框架跟踪值之间的关系,以实现高级优化,特别是边界检查消除。它维护一个“事实表”,记录属性,例如变量与其已知值范围之间的关系。
事实表是一种记录和推导值之间关系的复杂数据结构。它跟踪:
x < y、x == y)来源
该框架跟踪变量的界限(最小值和最大值),这使得推理值范围成为可能。这对于边界检查消除等优化至关重要。
来源
证明框架通过代码传播推导出的事实。例如,如果它知道 x < 10,并且代码根据 x < 5 进行分支,那么它可以在“真”分支中推断出 x < 5,在“假”分支中推断出 5 <= x < 10。
这种传播对于边界检查消除至关重要。对于像 a[i] 这样的数组访问,如果框架能够证明 0 <= i < len(a),那么就可以消除边界检查。
来源
SSA 重写系统应用基于模式的转换来优化代码。该系统由重写规则驱动,这些规则指定要匹配的代码模式及其替换项。
重写过程会重复应用规则,直到没有进一步的更改为止。
来源
规则定义在特定于平台和通用规则文件中。每个规则指定一个要匹配的模式以及要生成的替换代码。
例如,这条通用规则将乘以零替换为常量零:
(Mul8 (Const8 [0]) _) => (Const8 [0])
SSA 包包含以下规则文件:
generic.rules)RISCV64.rules、PPC64.rules、LOONG64.rules)来源
重写系统执行以下几种常见优化:
| 优化 | 示例 | 优点 |
|---|---|---|
| 常量折叠 | 2 + 3 → 5 | 在编译时计算常量 |
| 强度缩减 | x * 2 → x << 1 | 用更便宜的操作替换昂贵的操作 |
| 死代码消除 | 移除未使用的值 | 减小代码大小并提高缓存利用率 |
| 代数简化 | x + 0 → x | 移除不必要的运算 |
| 边界检查消除 | 移除冗余的数组边界检查 | 提高数组访问性能 |
来源
除了通用优化之外,Go 编译器还应用特定于体系结构的优化来利用目标处理器的特性。
每个支持的体系结构都有自己的一组重写规则,将通用操作映射到高效的特定于体系结构的指令。例如:
CLZ(计算前导零)CNTLZD 等操作进行前导零计数CLZV 和 CTZV 等指令优化位操作来源
不同的体系结构以各种方式优化位操作
| 操作 | RISCV64 | PPC64 | LOONG64 | AMD64 |
|---|---|---|---|---|
| LeadingZeros64 | CLZ | CNTLZD | CLZV | LZCNTQ/BSRQ |
| TrailingZeros64 | CTZ | - | CTZV | TZCNTQ |
| BitLen64 | - | - | - | 基于前导零 |
| PopCount64 | - | POPCNTD | VPCNT64 | POPCNTQ |
来源
SSA 包中的一项关键优化是分支消除,它会移除不必要的条件分支并简化控制流。
证明框架分析分支条件,以确定它们是始终为真、始终为假还是依赖于运行时值。当一个条件被证明是常量时,该分支就可以被简化或消除。
例如,在以下代码中:
当 x < 10 时,内部条件 x < 20 始终为真,因此可以消除内部 if 语句。
来源
证明框架的一个重要应用是边界检查消除。Go 通常会为数组和切片访问包含运行时边界检查,但当编译器可以证明它们永远不会失败时,这些检查可以被消除。
编译器可以在以下几种情况下证明边界是安全的:
来源
优化在编译过程中的几个阶段进行。
来源
为了更好地理解这些优化如何与更广泛的编译器集成,请参阅:
来源