Go 逃逸分析原理及常见问题

以前在 C++编程中要自己识别对象是在堆上还是栈上,如果把栈上地址(指针)返回了可能导致严重问题;同样,如果用 new/malloc 在堆上申请空间,可能造成内存泄漏问题。 Go 编译器实现了自动逃逸分析,变量对象的具体申请空间是根据分析情况决定的,这样省去了我们需要主动识别的烦恼,但同时带来了潜在的性能问题,需要注意。

  • 优点:

    • 对开发人员消除了堆和栈的区别,降低了复杂度
    • 自动分析,将不逃逸的创建在栈上——速度快;将逃逸的放在堆上,更灵活;
  • 编译时开启逃逸分析日志: go build -gcflags '-m -l'

基础知识

  • 堆的特点:

    • 分配空间大、无需提前知道容量大小
    • 分配速度慢,是栈上的 100 倍
  • 栈的特点:

    • 分配速度快
    • 分配空间相对小(Go 有优化)、需要在编译期就预测出容量

Go 逃逸分析知识点

  • 编译器会分析代码的特征和代码生命周期,Go 中的变量只有在编译器可以证明在函数返回后不会再被引用的,才分配到栈上,其他情况下都是分配到堆上。
    • 例如fmt.Println(...)函数,由于其中用到了反射导致编译器很难判定,所以变量就会分配到堆上
  • 关键词new并不能保证在堆上分配,仍然受编译器逃逸分析的控制
  • 函数的入参在 C++等语言中被作为函数的局部变量在栈上申请,但是在 Go 中同样由编译器逃逸分析决定
  • 有些时候,虽然分析结果为”没有逃逸”,但是由于对象容量过大、影响总体效率,还是会被分配在堆上
  • 不要盲目的使用结构体对象指针作为函数参数,虽然可以减少复制,但是在栈上复制传递的开销要远比在堆上创建的开销要小,还要根据具体情况而定
  • 在写代码时,尽量少写逃逸的变量,可以提高程序运行效率