1.介绍
熟悉 C / C++ 的读者朋友们应该都知道一个进程(应用程序)的虚拟内存空间划分为栈内存区和堆内存区。
栈内存区上对象的内存空间是自动分配和销毁的,使用者无需关心。但是,堆内存区上对象的内存空间是需要使用者自己管理,无形中增加了使用者的心智负担。
因此,一些高级语言会支持垃圾回收(GC),降低使用者内存管理的心智负担。支持垃圾回收的语言可以自动管理堆内存区上对象的内存空间。
Go 语言编译器负责决定把对象分配到栈上或堆上,比如一个对象在函数退出后就不可达(没有其他对象引用该对象)时,那就将该对象分配到栈上,反之,则分配到堆上。
如果一个对象被分配到堆上,就需要 Go 的垃圾回收管理该对象的内存空间。但是,垃圾回收是有代价的,它会占用系统开销。
所以,为了更大限度地降低垃圾回收占用的系统资源,提升应用程序本身可使用的系统资源,使用者就需要尽量减少堆内存分配,尽量多地使应用程序使用栈内存分配,尽量避免 Go 编译器通过逃逸分析优化后被分配到栈内存的对象逃逸到堆内存。
2.查看对象是否发生逃逸
Go 语言工具链提供了查看对象是否逃逸的方法,我们在执行 go build 时,配合使用参数 -gcflags 开启编译器支持的额外功能,例如:
go build -gcflasg '-m -m -l' main.go
- -m 用于输出编译器的执行细节,包括逃逸分析的执行。
- -l 用于禁用内联优化。
我们通过使用 Go 语言工具链对一段简单的示例代码进行查看对象是否发生逃逸。
func main() {
sum(1, 2)
}
func sum(a, b int) *int {
res := a + b
return &res
}
输出结果:
go build -gcflags '-m -m -l' main.go
# command-line-arguments
./main.go:8:2: res escapes to heap:
./main.go:8:2: flow: ~r0 = &res:
./main.go:8:2: from &res (address-of) at ./main.go:9:9
./main.go:8:2: from return &res (return) at ./main.go:9:2
./main.go:8:2: moved to heap: res
阅读上面这段代码,我们发现 sum 函数中的变量 res 逃逸到堆,也就是说 Go 编译器通过逃逸分析,决定将变量 res 分配到堆空间。
3.逃逸分析的作用
Go 语言编译器通过逃逸分析优化,将对象合理分配到栈空间和堆空间。
因为栈内存分配比堆内存分配更快,所以 Go 语言在编译时通过逃逸分析优化将不会发生逃逸的对象优先分配到栈空间。
因此,不仅降低堆空间内存分配的开销,同时,也可以降低垃圾回收占用的系统资源。
4.总结
本文我们介绍 Go 语言逃逸分析,它可以帮助使用者合理分配对象的内存空间。
我们知道分配到堆内存空间的对象,会导致 Go 执行垃圾回收,而垃圾回收会占用系统资源,降低应用程序本身可使用的系统资源。
所以,我们在实际项目开发中,可以借助 Go 工具链分析对象是否会发生逃逸,尽量避免一些不必要的对象逃逸。