编译优化
问题
Go 编译器有哪些优化?开发者能做什么帮助编译器?
答案
编译器自动优化
| 优化 | 说明 |
|---|---|
| 函数内联 | 小函数直接展开到调用点 |
| 逃逸分析 | 尽量在栈上分配 |
| 死代码消除 | 移除不可达代码 |
| 常量折叠 | 编译时计算常量表达式 |
| bounds check elimination | 编译器证明安全后移除边界检查 |
函数内联
内联将函数体直接嵌入调用者,减少函数调用开销:
// 小函数自动内联
func add(a, b int) int { return a + b }
// 查看内联决策
// go build -gcflags="-m" ./...
// ./main.go:5: can inline add
// ./main.go:10: inlining call to add
禁止内联(Benchmark 时避免影响):
//go:noinline
func add(a, b int) int { return a + b }
边界检查消除
// 编译器会插入运行时边界检查 → 有开销
func sum(s []int) int {
total := 0
for i := 0; i < len(s); i++ {
total += s[i] // 每次访问都检查 i < len(s)
}
return total
}
// 帮助编译器消除检查
func sum(s []int) int {
total := 0
_ = s[len(s)-1] // bounds check hint
for i := 0; i < len(s); i++ {
total += s[i] // 编译器知道 i < len(s),跳过检查
}
return total
}
PGO(Profile-Guided Optimization)
# Go 1.21+ 支持
# 放 default.pgo 在项目根目录,编译时自动启用
go build -pgo=auto ./cmd/server
PGO 根据 CPU Profile 优化热路径的内联、devirtualization 等,通常提升 2-7%。
编译标志
# 查看逃逸分析
go build -gcflags="-m" ./...
# 查看 SSA 优化
GOSSAFUNC=funcName go build
# 减小二进制体积
go build -ldflags="-s -w"
常见面试问题
Q1: Go 有 JIT 吗?
答案:没有。Go 是 AOT(Ahead-of-Time)编译,直接编译为机器码。优势是启动快、部署简单,劣势是无法做运行时热点优化。PGO 是编译时优化的补偿方案。
Q2: 如何让函数被内联?
答案:
- 函数体足够小(Go 编译器有内联预算)
- 不包含
defer、recover、select等复杂结构 - 不包含
for range中的闭包 - 可用
go build -gcflags="-m"查看哪些函数被内联