[toc]

go逃逸分析

什么是逃逸

内存逃逸到堆中

为什么要做逃逸分析

申请到栈内存性能好,不会引起GC,函数返回直接释放

申请到堆内存会导致gc,引起性能问题

如何分配:

  1. 如果函数外部没有引用,则优先放到栈中;
  2. 如果函数外部存在引用,则必定放到堆中;

常见的三种逃逸

指针逃逸

函数返回局部变量的指针导致指针逃逸

1
2
3
4
5
6
7
8
9
10
package main
func pointerEscapeFunc() *int{
var v int
return &v
}

func main(){
pointerEscapeFunc()//v会被分配到堆上
return
}
1
2
3
host$ go build -gcflags '-m -l' tem.go #-m打印信息,-l忽略inline信息
# command-line-arguments
./tem.go:3:6: moved to heap: v #可见v被分配到了堆上

栈空间不足

go的goroutine初始栈大小为2KB,go可以增大栈大小,但不可超过系统栈限制(使用ulimit -s查看),超过一定大小的变量将会逃逸到堆上,不同go版本大小限制不同

1
2
3
4
5
6
7
8
package main
func stackSpaceExhausted(){
v := make([]int,0,10000)
}

func main(){
stackSpaceExhausted()
}
1
2
3
host$ go build -gcflags '-m -l' tem.go 
# command-line-arguments
./tem.go:3:11: make([]int, 0, 10000) escapes to heap # 可见逃逸到了堆上,go1.15

闭包引用

函数类型也分两种,一种是函数字面量类型(未命名类型,func literal),另一种是函数命名类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
func outer() func() int{
var a int
return func() int{
a++
return a
}
}

func main(){
inner := outer()
print(inner())
}
1
2
3
4
host$ go build -gcflags '-m -l' tem.go 
# command-line-arguments
./tem.go:3:6: moved to heap: a
./tem.go:4:9: func literal escapes to heap

动态类型

对象大小不确定或作为不确定大小的参数时发生逃逸

1
2
3
4
5
6
package main
import "fmt"
func main(){
var a int
fmt.Println(a)//fmt.Println的入参是...interface{}
}
1
2
3
4
host$ go build -gcflags '-m -l' tem.go 
# command-line-arguments
./tem.go:7:13: main ... argument does not escape
./tem.go:7:13: a escapes to heap

逃逸分析使用

传值会拷贝对象,增加对象拷贝开销(听君一席话,胜似一席话),只读切内存小的结构体使用可以提高性能

传指针会导致内存逃逸到堆中,增加垃圾回收(GC)负担,对象需要频繁创建删除时,GC开销会特别大,影响性能,但在需要修改对象值、内存占用大的结构体中,传指针性能更好