【Go】02-逃逸分析(escape analysis)——未完
引入
C/C++
- 函数返回
局部变量:在C/C++中,一个函数可以直接返回函数中定义的局部变量,这里返回的是局部变量的副本(拷贝)。因为局部变量是分配在栈空间,因此在函数返回后,局部变量是被系统自动回收的, - 函数返回
局部变量地址:局部变量内存分配在栈空间,因为函数返回后,系统自动回收了函数里定义的局部变量,所以运行时去访问一个被系统回收后的地址空间,一定就会发生段错误,这是C/C++语言的特点。内存空间分配在堆空间中即可
1 |
|
Go
- 不同于C/C++,Go中函数内部局部变量,无论是动态new出来的变量还是创建的局部变量,它被分配在堆还是栈,是由编译器做
逃逸分析(Escape Analysis)之后做出的决定
1 | package main |
Go内存分配
堆空间
- 全局堆空间用来动态分配内存;堆区的内存一般由编译器和工程师自己共同进行管理分配,交给
Runtime GC来释放 - 堆上分配必须找到一块足够大的内存来存放新的变量数据;后续释放时,垃圾回收器扫描堆空间寻找不再被使用的对象
栈空间
- 每个
goroutine都有的自身栈空间 - 栈区的内存一般由编译器自动进行分配和释放,其中存储着函数的入参以及局部变量,这些参数会随着函数的创建而创建,函数的返回而销毁(即 CPU push & release
分配规则
由编译器做逃逸分析决定
Go: Frequently Asked Questions (FAQ)
How do I know whether a variable is allocated on the heap or the stack?
From a correctness standpoint, you don’t need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.
The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function’s stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.
In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.
逃逸分析
定义
- 编译器决定内存分配位置的方式,就称之为
逃逸分析(escape analysis)。逃逸分析由编译器完成,作用于编译阶段 - 引起内存逃逸的关键:编译器在编译的时候无法确定确定变量的生命周期,只能在运行时控制了
- 在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法,简单来说就是分析在程序的哪些地方可以访问到该指针;再往简单的说,Go是通过在编译器里做逃逸分析(escape analysis)来决定一个对象放栈上还是放堆上,不逃逸的对象放栈上,可能逃逸的放堆上
- 发现
变量在退出函数后没有用了,那么就把丢到栈上,毕竟栈上的内存分配和回收比堆上快很多 - 函数内的普通变量经过
逃逸分析后,发现在函数退出后变量还有在其他地方上引用,那就将变量分配在堆上。做到按需分配
- 发现
为什么需要逃逸分析
- 减少
GC压力,栈上的变量,随着函数退出后系统直接回收,不需要GC标记后再清除 - 减少内存碎片的产生
- 减轻分配堆内存的开销,提高程序的运行速度
查看对象是否发生逃逸
在
Go中通过逃逸分析日志来确定变量是否逃逸,开启逃逸分析日志1
2// go build也可以
go run -gcflags '-m -l' main.go-m会打印出逃逸分析的优化策略,实际上最多总共可以用 4 个-m,但是信息量较大,一般用 1 个就可以了-l会禁用函数内联,在这里禁用掉内联能更好的观察逃逸情况,减少干扰
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35package main
import "fmt"
func fun() *int {
tmp := 1
return &tmp
}
func fun2() int {
val := 2
return val
}
func main() {
var p *int
p = fun()
fmt.Printf("%d", *p)
t := fun2()
fmt.Printf("%d", t)
}
/*
# command-line-arguments
./main.go:5:6: can inline fun
./main.go:10:6: can inline fun2
./main.go:17:12: inlining call to fun
./main.go:18:15: inlining call to fmt.Printf
./main.go:19:11: inlining call to fun2
./main.go:20:12: inlining call to fmt.Printf
./main.go:6:5: moved to heap: tmp
./main.go:18:15: ... argument does not escape
./main.go:18:22: *p escapes to heap
./main.go:20:12: ... argument does not escape
./main.go:20:13: t escapes to heap
*/- 其中
moved to heap: tmp和xxx escapes to heap即发生逃逸,分配在堆上
- 其中
引发逃逸的情形
总结:多级间接赋值容易导致逃逸
这里的多级间接指的是,对某个引用类对象中的引用类成员进行赋值。Go 语言中的引用类数据类型有
func,interface,slice,map,chan,*Type(指针)记住公式
Data.Field = Value,如果 Data, Field 都是引用类的数据类型,则会导致 Value 逃逸。这里的等号 = 不单单只赋值,也表示参数传递假设一个变量
data是以下几种类型,相应的可得出结论1
2
3
4
5
6
7
8
9
10[]interface{}: data[0] = 100 会导致 100 逃逸
map[string]interface{}: data["key"] = "value" 会导致 "value" 逃逸
map[interface{}]interface{}: data["key"] = "value" 会导致 key 和 value 都逃逸
map[string][]string: data["key"] = []string{"hello"} 会导致切片逃逸
map[string]*int: 赋值时 *int 会 逃逸
[]*int: data[0] = &i 会使 i 逃逸
func(*int): data(&i) 会使 i 逃逸
func([]string): data([]{"hello"}) 会使 []string{"hello"} 逃逸
chan []string: data <- []string{"hello"} 会使 []string{"hello"} 逃逸
......
内存逃逸的弊端
提问:函数传递指针真的比传值效率高吗?
我们知道传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,也可能会增加GC的负担,所以传递指针不一定是高效的
TODO
- 编译器如何实现逃逸分析
- 逃逸与否的性能比较
参考
待看
- Language Mechanics On Escape Analysis
- GO学习笔记之(一) Stack Or heap? 这是一个问题
- Go 逃逸分析
- Stack memory and escape analysis in Go language
- Go: Introduction to the Escape Analysis
- Golang Memory Escape In-Depth Analysis
- Escape Analysis in Golang
- why the below two code cause escape in golang
- Golang的逃逸分析怎么实现,方法和操作是怎样




