逃逸分析
概述
默认分配策略
在没有逃逸分析的情况下,编译器可能会采用以下保守的默认分配策略:
- 局部变量:通常会分配在栈上,但如果编译器无法确定其生命周期,则可能会分配在堆上。
- 指针变量:如果局部变量的地址被取出并传递到其他函数,编译器可能会将其分配在堆上。
- 闭包捕获的变量:这些变量通常会分配在堆上,因为闭包可能在函数返回后仍然存在。
逃逸分析就是,确定哪些变量可以分配在栈上,哪些需要分配在堆上。禁用优化后,更多的变量可能会分配到堆上,增加垃圾收集的负担。
在 Go 语言中,判断一个对象是分配在栈上还是堆上可以通过查看编译器的逃逸分析结果来实现。Go 编译器提供了一个选项,可以在编译时输出逃逸分析的信息,这样你就可以知道哪些变量是分配在堆上,哪些是分配在栈上。
使用 gcflags 选项查看逃逸分析结果
你可以使用 -gcflags="-m" 选项来编译你的程序,并查看编译器输出的逃逸分析信息。具体步骤如下:
- 创建一个示例程序:
package main
import "fmt"
func createMap() map[string]int {
m := make(map[string]int)
m["key"] = 42
return m
}
func createSlice() []int {
s := make([]int, 10)
s[0] = 42
return s
}
func main() {
m := createMap()
fmt.Println(m)
s := createSlice()
fmt.Println(s)
}
使用 gcflags="-m" 选项编译程序:
go build -gcflags="-m" main.go
查看编译器输出:
编译器会输出逃逸分析的信息,类似如下:
command-line-arguments
./main.go:6:6: can inline createMap
./main.go:12:6: can inline createSlice
./main.go:7:13: make(map[string]int) does not escape
./main.go:8:13: m["key"] escapes to heap
./main.go:8:13: &m["key"] escapes to heap
./main.go:13:13: make([]int, 10) does not escape
./main.go:14:13: s[0] escapes to heap
解释编译器输出
- does not escape:表示变量没有逃逸,通常会分配在栈上。
- escapes to heap:表示变量逃逸了,通常会分配在堆上。
在上述输出中:
make(map[string]int) does not escape表示map的初始化没有逃逸,可能会分配在栈上。m["key"] escapes to heap表示map的元素访问逃逸了,导致map被分配在堆上。make([]int, 10) does not escape表示slice的初始化没有逃逸,可能会分配在栈上。s[0] escapes to heap表示slice的元素访问逃逸了,导致slice的底层数组被分配在堆上。