本页面描述了 Go 编译器执行的优化过程。它解释了编译器如何分析和转换代码以提高性能,同时保持语义不变。有关函数内联等具体优化的信息,请参阅 函数内联。有关 SSA 优化的详细信息,请参阅 SSA 优化。
Go 编译器包含一套全面的优化,这些优化在编译的不同阶段运行。大多数优化在类型检查之后、机器码生成之前运行,将代码转换为更高效的形式,同时保持原始语义。这些优化主要在静态单赋值(SSA)框架中实现。
可以使用 -N 标志(禁用所有优化)来禁用优化,或使用更具体的标志进行调整。可以使用 -m 标志监控优化决策的详细程度。
来源: src/cmd/compile/internal/inline/inl.go1-50 src/cmd/compile/internal/ssa/prove.go1-50 src/cmd/compile/internal/ssa/rewrite.go1-50 src/cmd/compile/internal/base/flag.go56-81
优化通过几个编译器标志进行控制。其中最重要的标志是
| 标志 | 描述 |
|---|---|
-N | 禁用优化 |
-l | 控制内联(级别0禁用) |
-m | 打印优化决策(值越高,细节越多) |
-d=ssa/prove/debug=1 | 为值证明启用调试输出 |
-d=checkptr | 启用指针检查(影响优化) |
默认优化级别启用大多数优化,这等同于设置 -N=0 和 -l=1。
来源: src/cmd/compile/internal/base/flag.go56-76 src/cmd/compile/internal/inline/inl.go10-25
函数内联是最重要的优化之一。它用被调用函数的代码替换函数调用,消除了调用开销,并允许对组合代码进行进一步优化。
内联由预算系统控制。每个函数都有一个基于其复杂度的成本,只有当其成本低于内联预算阈值时才能将其内联。
编译器使用复杂的启发式方法来决定内联哪些函数
//go:noinline 的函数永远不会被内联剖析引导优化 (PGO) 可以影响内联决策,允许在“热”调用点进行更积极的内联。
来源: src/cmd/compile/internal/inline/inl.go48-187 src/cmd/compile/internal/inline/inl.go210-360 src/cmd/compile/internal/inline/inl.go901-970
最有力的优化之一是 SSA 包中的值证明系统。该系统分析值之间的关系,并可以证明如下条件:
当编译器可以保证它们永远不会失败时,这些证明用于消除不必要的跳转、边界检查和其他运行时安全检查。
证明系统通过部分排序集跟踪变量之间的关系,并通过 SSA 图传播已知的界限和约束。它可以消除
a[i] 后面紧跟着 a[i])if i < len(a) { a[i] = ... })来源: src/cmd/compile/internal/ssa/prove.go118-230 src/cmd/compile/internal/ssa/prove.go380-480 test/prove.go15-40
Go 编译器使用声明式规则系统来简化常见的代码模式。这些规则定义在 _gen/generic.rules 中,并自动应用以将代码转换为更高效的形式。
通用重写规则的例子包括
1 + 2 → 3x * 1 → x, x * 2 → x << 1true || x → true*(&x) → x通用规则与架构无关,并在特定于架构的优化之前应用。这些规则使用 SSA 表示的模式匹配来实现。
来源: src/cmd/compile/internal/ssa/_gen/generic.rules1-60 src/cmd/compile/internal/ssa/_gen/generic.rules187-207 src/cmd/compile/internal/ssa/rewritegeneric.go1-50
在通用优化之后,将应用特定于架构的重写。这些会将操作转换为目标架构最有效的指令。
特定于架构的优化的例子
例如,Power9/Power10 机器具有用于整数乘法加法等操作的专用指令,可用于组合操作。
来源: src/cmd/compile/internal/ssa/rewritePPC64.go1-30 src/cmd/compile/internal/ssa/_gen/PPC64.rules1-40 src/cmd/compile/internal/ppc64/ssa.go1-20
死代码消除会移除不影响程序可观察行为的代码。这包括
x = x)基于 SSA 的优化器可以有效地识别和移除这些结构。死代码消除与其他优化交织在一起,因为每次转换都可能为消除创造新的机会。
来源:src/cmd/compile/internal/ssa/rewrite.go82-93
Go 1.20 引入了剖析驱动优化 (PGO),它使用运行时剖析数据来做出更好的优化决策。启用后,PGO 会影响
PGO 在内联方面尤其有效,因为它可以为频繁调用的函数超出正常的内联预算。
来源:src/cmd/compile/internal/inline/inl.go104-140 src/cmd/compile/internal/inline/inl.go901-965
编译器提供了多种方法来调试和理解优化
-m 标志显示优化决策-d=ssa/prove/debug=1 显示值证明的详细信息-N 禁用优化-l=0 禁用内联特别是对于内联,您可以使用
-m 显示哪些函数被内联-m=2 显示内联决策的详细原因通过这些调试标志,开发人员可以了解为什么对他们的代码应用了某些优化或未应用。
来源:src/cmd/compile/internal/base/debug.go19-83 src/cmd/compile/internal/inline/inl.go290-300
Go 编译器的优化随着每个版本的发布而不断发展。一些值得注意的发展包括
优化框架旨在具有可扩展性,允许在不干扰现有优化的前提下添加新优化。
来源:src/cmd/compile/internal/base/debug.go19-50 src/cmd/compile/internal/inline/inl.go48-60