Go逃逸分析
source link: https://doumao.cc/index.php/%E7%BC%96%E7%A8%8B/Go%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
之前写过一篇关于Go内存分配的文章 - Go内存分配,讲的是Go堆内存的申请过程。对象除了可以在堆上分配,还有一个我们很熟悉的地方可以分配,栈。
栈内存分配
每一个协程都有自己的栈,协程访问栈的对象时,不需要加锁等同步操作
。
下面是一个例子:
调用main
函数,Go在栈上分配内存
调用square
函数,Go在栈上分配内存
当square
执行完返回main
时,上一步调用square
时,分配的内存不会被回收。仅仅是移动了栈指针的位置。
调用println
函数,Go移动栈指针的位置来分配内存。
可以看出,在栈上分配内存的开销是很小的,只需要上下移动栈指针的位置来申请和释放内存。所以Go也倾向于在栈上分配内存。(Go prefers allocation on the stack
)
PS: 相比栈,在堆上分配内存的过程比较复杂,而且会使用垃圾回收来释放内存,垃圾回收时,会对程序造成延迟和降低吞吐量。
同时,Go使用的协程栈初始大小是2k,但可以动态的扩容和缩容。在开销上比线程栈小得多。
我们已经知道,在栈上分配内存远比在堆上分配内存好,但不是所有的对象都可以在栈上分配。
下面是一个例子
调用main
函数,Go在栈上分配内存
调用answer
函数,Go在栈上分配内存
answer
执行完返回main
,此时Go移动了栈指针,上一步为answer
分配的内存限制是无效的。
调用println
函数,Go移动栈指针来分配内存,此时覆盖了调用answer
时分配的内存,指针指向的内存里的值已经被修改。
Go编译器使用了逃逸分析
来将对象分配到堆上,来解决上面的问题。
同样的程序
调用main
函数,Go在栈上分配内存
调用answer
函数,Go编译器知道在栈上为对象x分配内存是不安全的,此时会在堆上为对象x分配内存。
answer
执行完返回main
,此时指针n指向的是堆上的对象x
只有编译器知道对象应该被分配到哪里
我们无法决定对象被分配到栈还是堆,只有编译器知道。当编译器无法证明
函数内的局部在函数执行完后,不会被外部引用,编译器会将变量分配到堆上,避免悬挂指针
。
我们可以使用go build -gcflags="-m 1"来了解对象如何分配。
一个我们平时写程序时注意的地方
左边的程序,每次调用read
,都会在堆
上分配对象。
这其实也是为什么io.Reader
接口为什么将Read
定义为Read(p []byte)(n int, err error)
,而不是Read(n int) (b []byte, err error)
的原因。
Understanding Allocations: the Stack and the Heap - GopherCon SG 2019
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK