Go中的逃逸现象 | 青训营笔记

55 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天

概念

在一段程序中,每一个函数都会有自己的内存区域存放自己的局部变量、返回地址等,这些内存会由编译器在栈中进行分配,每一个函数都会分配一个栈桢,在函数运行结束后进行销毁,但是有些变量我们想在函数运行结束后仍然使用它,那么就需要把这个变量在堆上分配,这种从"栈"上逃逸到"堆"上的现象就成为内存逃逸

在栈上分配的地址,一般由系统申请和释放,不会有额外性能的开销,比如函数的入参、局部变量、返回值等。在堆上分配的内存,如果要回收掉,需要进行GC,那么GC一定会带来额外的性能开销。编程语言不断优化GC算法,主要目的都是为了减少GC带来的额外性能开销,变量一旦逃逸会导致性能开销变大。

逃逸机制

  1. 如果外部没有引用,则优先放到栈中;
  2. 如果外部存在引用,则一定放到堆中;
  3. 如果栈放不下,则一定放到堆中;

例子

指针逃逸

func esape1() *int{
    var a int = 1
    return &a
}
func main() {
    escape1()
}

可以通过go build -gcflags=-m main.go查看逃逸情况

函数返回值为局部变量的指针, 函数虽然退出了,但是因为指针的存在,指向的内存不能随着函数的结束而回收,因此只能分配在堆上。

栈空间不足

func escape2() {
    s := make([]int, 0, 10000)
    for index,_ := range s{
        s[index] = index
    }
}
func main(){
    escape2()
}

[]interface{}数据类型,通过[]赋值必定会出现逃逸。

package main

func main() {
    data := []interface{}{100, 200}
    data[0] = 100
}

map[string]interface{}类型尝试通过赋值,必定会出现逃逸。

package main

func main() {
    data := make(map[string]interface{})
    data["key"] = 200
}

[]*int数据类型,赋值的右值会发生逃逸现象。

package main

func main() {
    a := 10
    data := []*int{nil}
    data[0] = &a
}

总结

  1. 栈上分配内存比堆中分配内存效率高
  2. 栈上分配的内存不需要GC处理,而堆需要
  3. 逃逸分析目的是决定内存分配地址是栈还是堆
  4. 逃逸分析在编译阶段完成