原创文章,转载请注明出处:服务器非业余研究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)的大小,只需要扩大栈的大小到之前的两倍,然后再尝试就行,如果栈大小还是不能满足我们的溢出检测需要的大小,那就再扩大到两倍,这样来回重复直至栈大小足够为止。
- defer调用中的函数对象的闭包参数(closure)
- 已经传递给溢出栈的函数对象
- 已经传递给溢出栈的函数对象的闭包参数
有疑问加站长微信联系(非本文作者)