2

Go逃逸分析

 2 years ago
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.
neoserver,ios ssh client

之前写过一篇关于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

An Insight into Go Garbage Collection.pdf

栈空间管理


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK