Go1.3新特性 栈的全新实现—连续栈(1)

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

原创文章,转载请注明出处:服务器非业余研究http://blog.csdn.net/erlib 作者Sunface


前言

   在之前的Go1.3改动中提到过栈的实现从分段实现(segment stack)变为连续栈实现(Contiguous statck),这个改动不可谓不大,那会给我们带来什么影响呢?请参见下文


连续栈

  给每个goroutine 都分配一块内存作为连续的栈,通过重新分配/复制来增长。


为什么需要连续栈

  当前的栈分割技术有一个很大的问题:"hot split"(热分割)——如果栈快满的时候,一个额外的调用就会强制分配新的栈块(Stack chunk),当调用返回的时候,新分配的栈块就会被释放回收,这种情况下,如果同一个调用连续执行,过度的alloc/free会导致系统性能的显著降低。

  使用栈分割的话,栈的分配和释放工作永远都不会完成——每次栈大小到达临界值的时候,都需要做额外的工作。

  但是当使用连续栈技术的时候,就可以避免上述这些问题。栈的大小会增长到需要的大小,仅此而已,再无多余的操作(除了内存收缩shrinking,见下文)



 如何做到呢?

   要怎么样才能移动一个栈?我们的编译器的逃逸分析(escape analysis)提供了一个很重要的不变量:栈中指向某块数据的指针在调用树中(call tree)只能按顺序来传递,其它任何形式的逃逸(写进全局变量,返回给父调用,写入堆(heap)中)都会阻止那块数据在该栈上的分配。这个不变量意味着当有指针指向栈中的这块数据时,这个指针是存在该栈中的(也有例外,请看下文),这样当需要复制栈的时候就可以很轻松的寻找和更新了。


溢出检测

      栈溢出检测跟栈段分割溢出检测非常相像,它们主要的区别就是我们不再需要知道参数的大小,因为不再需要把参数复制到新的栈段上。

甚至我们不需要知道栈桢(frame)的大小,只需要扩大栈的大小到之前的两倍,然后再尝试就行,如果栈大小还是不能满足我们的溢出检测需要的大小,那就再扩大到两倍,这样来回重复直至栈大小足够为止。


复制
当在检测到栈溢出时:
首先分配一个比之前栈大一些的新栈,栈大小应该以指数的方式增长(2^n),也可能是以某个大小的倍数方式增长(因此,内存分配会更有效率),目前计划:每次分配是    按2^n来增长。
然后把旧栈复制到新栈,因为栈是 *byte组成的数组,所以每个*byte都会从旧栈拷贝到新栈。

在复制的时候我们需要小心那些看上去像指针的整数,因此我们需要一个数据结构来告诉我们:栈上的每个指针大小的整数是一个指针还是其他的数据类型?幸运的是这种数据结构已经有了,这样可以让我们的GC更加精确。
任何从外部(被调用者外部)进入栈的指针都需要被调整,虽然说尽管有逃逸分析,但是还有一些漏网之鱼,包括:
defer调用中的函数对象
defer调用中的参数
  •  defer调用中的函数对象的闭包参数(closure)
  •  已经传递给溢出栈的函数对象
  •  已经传递给溢出栈的函数对象的闭包参数


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

本文来自:CSDN博客

感谢作者:abv123456789

查看原文:Go1.3新特性 栈的全新实现—连续栈(1)

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

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