0%

golang 逃逸分析

[TOC]

逃逸分析

所谓逃逸分析(Escape analysis)是指由编译器决定内存分配的位置,不需要程序员指定。所谓逃逸,就是指对象逃到了堆内存上,而不是分配在 stack 上

在函数中申请一个新的对象:

  • 如果分配在栈中,则函数执行结束可自动将内存回收;
  • 如果分配在堆中,则函数执行结束可交给GC(垃圾回收)处理;

逃逸场景(什么情况才分配到堆中)

指针逃逸

Go可以返回局部变量指针,这其实是一个典型的变量逃逸案例,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

type Student struct {
Name string
Age int
}

func StudentRegister(name string, age int) *Student {
s := new(Student) //局部变量s逃逸到堆

s.Name = name
s.Age = age

return s
}

func main() {
StudentRegister("Jim", 18)
}

分析结果

后面不再写分析结果, 感兴趣的读者可以自己执行逃逸分析编译

1
2
3
4
$ go run -gcflags "-m -l" demo.go
# command-line-arguments
.\demo.go:8:22: leaking param: name
.\demo.go:9:10: new(Student) escapes to heap

栈空间不足逃逸(空间开辟过大)

实际上当栈空间不足以存放当前对象时或无法判断当前切片长度时会将对象分配到堆中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

func main() {
data1 := make([]int, 1000)
data2 := make([]int, 0)
// 数据量太大, 逃逸到堆中
data3 := make([]int, 10000000)
mapData := make(map[string]int)
sliceMap(data1, data2, data3, mapData)
}

func sliceMap(data1 []int, data2 []int, data3 []int, mapData map[string]int) []int {
data1[0] = 3
data2 = append(data2, 3)
mapData["a"] = 1
return data1
}

动态类型逃逸(不确定长度大小)

1
2
3
4
5
6
7
8
package main

import "fmt"

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

闭包逃逸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func Fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}

func main() {
f := Fibonacci()

for i := 0; i < 10; i++ {
fmt.Printf("Fibonacci: %d\n", f())
}
}

结果分析

1
2
3
4
5
6
7
8
# Fibonacci()函数中原本属于局部变量的a和b由于闭包的引用,不得不将二者放到堆上,以致产生逃逸。
$ go run -gcflags "-m -l" demo.go
# command-line-arguments
.\demo.go:6:2: moved to heap: a
.\demo.go:6:5: moved to heap: b
.\demo.go:7:9: func literal escapes to heap
.\demo.go:17:13: ... argument does not escape
.\demo.go:17:34: f() escapes to heap

总结

  • 栈上分配内存比在堆中分配内存有更高的效率
  • 栈上分配的内存不需要GC处理
  • 堆上分配的内存使用完毕会交给GC处理
  • 逃逸分析目的是决定内分配地址是栈还是堆
  • 逃逸分析在编译阶段完成

提问

提问:函数传递指针真的比传值效率高吗?

我们知道传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,也可能会增加GC的负担,所以传递指针不一定是高效的。

参数说明

-gcflags “m” 参数,这个参数会打印逃逸分析的详细信息

-l 参数是防止函数identity被内联

优化

sync.Pool

参考

原文

golang 逃逸分析