介绍
在「Go 语言逃逸分析」中,我们了解到内存分配的相关知识,栈空间分配开销小,堆空间分配开销大。
Go 语言编译器可以通过逃逸分析决定内存分配到栈空间或堆空间。但是,分配到栈空间的对象在某些情况中会逃逸到堆空间。我们可以使用 Go 工具链查看对象是否发生内存逃逸。
为了提升 Go 应用程序的性能,我们应该避免 Go 应用程序中出现内存逃逸的现象,本文我们介绍 Go 语言内存逃逸的几种典型案例。
内存逃逸案例
指针逃逸
示例代码:
func main() { pointerEscape(1, 2) } // pointerEscape 指针逃逸 func pointerEscape(a, b int) *int { sum := a + b return &sum }
输出结果:
go build --gcflags '-m -m -l' main.go # command-line-arguments ./main.go:9:2: sum escapes to heap: ./main.go:9:2: flow: ~r0 = &sum: ./main.go:9:2: from &sum (address-of) at ./main.go:10:9 ./main.go:9:2: from return &sum (return) at ./main.go:10:2 ./main.go:9:2: moved to heap: sum
阅读上面这段代码,我们创建一个函数 pointerEscape
,函数内部创建一个局部变量 sum
,返回结果是该变量的指针。
通过执行 go build --gcflags '-m -m -l' main.go
的输出结果,我们发现函数中定义的局部变量 sum 逃逸到堆空间,这就是所谓的指针逃逸。
函数 pointerEscape
的局部变量 sum
本来应该在函数结束时被回收,但是在 main 函数中会继续使用 sum
变量的内存地址,导致变量 sum
被逃逸到堆上。
如果想要避免示例函数的返回结果出现内存逃逸,可以使用值类型的返回结果,这样就带来另外一个问题。
如果返回结果是一个比较大的变量,比如返回结果是较大的结构体类型的变量,我们使用值类型将会造成比较大的内存占用。
所以,我们在实际项目开发中,需要根据实际情况,合理使用返回结果的类型。
动态类型逃逸
示例代码:
func main() { fmt.Println("hello world") }
输出结果:
go run -gcflags '-m -m -l' main.go # command-line-arguments ./main.go:6:14: "hello world" escapes to heap: ./main.go:6:14: flow: {storage for ... argument} = &{storage for "hello world"}: ./main.go:6:14: from "hello world" (spill) at ./main.go:6:14 ./main.go:6:14: from ... argument (slice-literal-element) at ./main.go:6:13 ./main.go:6:14: flow: {heap} = {storage for ... argument}: ./main.go:6:14: from ... argument (spill) at ./main.go:6:13 ./main.go:6:14: from fmt.Println(... argument...) (call parameter) at ./main.go:6:13 ./main.go:6:13: ... argument does not escape ./main.go:6:14: "hello world" escapes to heap
阅读上面这段代码,我们在 main 函数中,使用 fmt.Println()
打印字符串 hello world
。
通过执行 go run -gcflags '-m -m -l' main.go
的输出结果,我们发现使用 fmt.Println()
打印的字符串 hello world
逃逸到堆上,这就是所谓的动态类型逃逸。
因为 fmt.Println()
接收的参数是空接口类型,Go 编译器无法确定入参变量的具体类型,所以此类情况变量也会逃逸到堆上。
03
总结
本文我们介绍两个典型的内存逃逸的案例,除此之外,以下几种情况,也会发生内存逃逸。
- 发送指针或带有指针的值到 channel 中。
- 在一个切片上存储指针或带指针的值。
- slice 的底层数组被重新分配(append 超出其容量时)。
感兴趣的读者朋友们,可以自行编写上述几种情况的示例代码,验证是否会发生内存逃逸。
推荐阅读:
参考资料: