站长网 语言 Go 中的分段栈和连续栈的不同

Go 中的分段栈和连续栈的不同

# 分段栈在 Go 1.3 版本之前 ,使用的栈结构是分段栈,随着goroutine 调用的函数层级的深入或者局部变量需要的越来越多时,运行时会调用 runtime.morestack 和 runtime.newstack创建一个新的栈空间,这些栈空间是不连续的,但是当前 goroutine 的多个栈空间

# 分段栈

在 Go 1.3 版本之前 ,使用的栈结构是分段栈,随着goroutine 调用的函数层级的深入或者局部变量需要的越来越多时,运行时会调用 runtime.morestack 和 runtime.newstack创建一个新的栈空间,这些栈空间是不连续的,但是当前 goroutine 的多个栈空间会以双向链表的形式串联起来,运行时会通过指针找到连续的栈片段。

 

分段栈虽然能够按需为当前 goroutine 分配内存并且及时减少内存的占用,但是它也存在一个比较大的问题:

 

如果当前 goroutine 的栈几乎充满,那么任意的函数调用都会触发栈的扩容,当函数返回后又会触发栈的收缩,如果在一个循环中调用函数,栈的分配和释放就会造成巨大的额外开销,这被称为热分裂问题(Hot split)。

 

为了解决这个问题,Go 在 1.2 版本的时候不得不将栈的初始化内存从 4KB 增大到了 8KB。后来把采用连续栈结构后,又把初始栈大小减小到了 2KB。

 

# 连续栈

连续栈可以解决分段栈中存在的两个问题,其核心原理就是每当程序的栈空间不足时,初始化一片比旧栈大两倍的新栈并将原栈中的所有值都迁移到新的栈中,新的局部变量或者函数调用就有了充足的内存空间。使用连续栈机制时,栈空间不足导致的扩容会经历以下几个步骤:

 

调用用 runtime.newstack 在内存空间中分配更大的栈内存空间;

使用 runtime.copystack 将旧栈中的所有内容复制到新的栈中;

将指向旧栈对应变量的指针重新指向新栈;

调用 runtime.stackfree销毁并回收旧栈的内存空间;

copystack会把旧栈里的所有内容拷贝到新栈里然后调整所有指向旧栈的变量的指针指向到新栈, 我们可以用下面这个程序验证下,栈扩容后同一个变量的内存地址会发生变化。

 

package main 

 

func main() { 

    var x [10]int 

    println(&x) 

    a(x) 

    println(&x) 

 

//go:noinline 

func a(x [10]int) { 

    println(`func a`) 

    var y [100]int 

    b(y) 

 

//go:noinline 

func b(x [100]int) { 

    println(`func b`) 

    var y [1000]int 

    c(y) 

 

//go:noinline 

func c(x [1000]int) { 

    println(`func c`) 

程序的输出可以看到在栈扩容前后,变量x的内存地址的变化:

 

0xc000030738 

… 

… 

0xc000081f38 

是不是很简单呢?

本文来自网络,不代表站长网立场,转载请注明出处:https://www.tzzz.com.cn/html/biancheng/yuyan/2021/1104/19869.html

作者: dawei

【声明】:站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。
联系我们

联系我们

0577-28828765

在线咨询: QQ交谈

邮箱: xwei067@foxmail.com

工作时间:周一至周五,9:00-17:30,节假日休息

返回顶部