Go逃逸分析

131 阅读2分钟

Go内存管理

go在程序运行时分配了不同的区空间来管理内存。

无标题-2022-09-19-1545.excalidraw.png

何为逃逸

go会将要执行的函数放入栈区,结束时会在栈区空间中释放,里面的局部变量也跟着释放。然而当该函数返回某个局部变量,并被外部函数调用时,该局部变量就不应该被放弃,于是go内存管理会在堆区中开辟一块空间,用于储存该局部变量的数据。我们看起来就像该变量从栈区跑到了堆区,便称之为逃逸

而由编译器决定某变量分配到栈上还是堆上,便叫做逃逸分析

有哪些逃逸

interface{} 逃逸

空接口interface{}可以表示任意类型,当作为函数参数传参时,编译器无法确定其类型,一次也会发生逃逸,分配到堆上。

示例:

func main() {
    s := "abc"
    fmt.Println(s)
}

image.png

这里使用fmt.Println打印了s变量,由于fmt.Println的参数为interface{}, 于是发生逃逸。

type any = interface{}

func Println(a ...any) (n int, err error) {
   return Fprintln(os.Stdout, a...)
}

指针逃逸

函数声明一个对象,之后返回该对象的指针,那么随着函数的结束,该指针并未被回收,而是分配到了堆上,即指针逃逸了。

示例:

package main

type student struct {
   name string
}

func demo(name string) *student {
   s := &student{name: name}
   return s
}

func main() {
   _ = *demo("李四") // 使用返回值
}

使用 go run -gcflags '-m -l' escape.go 来查看逃逸情况。

image.png

xxx escapes to heap 即表示xxx逃逸了,这里发现student指针发生逃逸。

闭包逃逸

函数中嵌套一层函数,那么嵌套函数可访问上一层函数的作用域,此为闭包。而在闭包中,嵌套函数由于使用了上层函数的变量,该变量无法随着嵌套函数释放而释放,因此也被逃逸到堆上。

示例

func demo() func() int {
   n := 0
   return func() int {
      n++
      return n
   }
}

func main() {
   demo()
}

image.png

总结

  1. 逃逸分析是在编译阶段执行
  2. 由于栈上分配内存比堆上分配内存效率更高,因此减少逃逸能提高性能

推荐阅读

Go 逃逸分析