Golang 内存逃逸分析

马谦的博客 · · 2176 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

0x01 什么是逃逸

第一次听说逃逸是在雨痕学堂,一脸懵逼的百度了半天也没找到一个明确的说法,直到昨天在论坛上看到一篇关于变量逃逸的文章才明白。

因为函数都是运行在栈上的,在栈声明临时变量分配内存,函数运行完毕再回收该段栈空间,并且每个函数的栈空间都是独立的,其他代码都是不可访问的。

但是在某些情况下,栈上的空间需要在该函数被释放后依旧能访问到,这时候就涉及到内存的逃逸了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type data struct {
name string
}
//go:noinline
func f1()data{
d := data{"maqian"}
return d
}
//go:noinline
func f2() *data {
d := data{"maqian"}
return &d
}
func main(){
d1 := f1()
d2 := f2()
}

f1和f2两个函数都是创建一个变量返回,不同的是f1返回变量副本,f2返回变量指针。在大多数语言例如C/C++,类似f2的函数是不对的,因为d是一个临时变量,return过后就会被释放掉,返回毫无意义。但是在golang中,这种语法是允许的,它能正确的把d的地址返回到上层调用函数而不被释放。

正如上面所说,该函数在运行完毕后肯定是要释放的,内部分配的临时内存也要释放,所以d也应该被释放。而为了让d能被正确返回到上层调用,golang采取了一种内存策略,把d从栈拿到堆的中去,此时d就不会跟随f2一同消亡了,这个过程就是一次逃逸。

0x02 编译报告

代码中两个函数上方的go:noinline 注释是一个编译标记,让编译器不内联当前代码方便观察编译状态。因为这里代码十分简单,不给标记编译器会自动内联该段代码看不到效果。

在编译时可以通过gcflags 选项带上-m 参数查看到编译状态,-m 一共可以携带四个:

1
2
3
4
5
6
7
8
9
10
[ma@ma escape_analysis]$ go build -o app -gcflags "-m -m"
# _/data/code/go/src/test-src/level6-other/escape_analysis
./escape.go:8:6: cannot inline f1: marked go:noinline
./escape.go:14:6: cannot inline f2: marked go:noinline
./escape.go:19:6: cannot inline main: non-leaf function
./escape.go:16:9: &d escapes to heap
./escape.go:16:9: from ~r0 (return) at ./escape.go:16:2
./escape.go:15:20: moved to heap: d
./escape.go:20:8: d1 declared and not used
./escape.go:21:8: d2 declared and not used

通过第四行和第五行输出很直接就能看出,在代码的第16行,即函数f2return 处,参数d产生了逃逸行为。


有疑问加站长微信联系(非本文作者)

本文来自:马谦的博客

感谢作者:马谦的博客

查看原文:Golang 内存逃逸分析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2176 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传