更新 Go 1.4(2014 年第四季度)
更改为运行时:
在 Go 1.4 之前,运行时(垃圾收集器、并发支持、接口管理、映射、切片、字符串等)大部分是用 C 编写的,并带有一些汇编程序支持。
在 1.4 中,大部分代码已被转换为 Go,以便垃圾收集器可以在运行时扫描程序堆栈并获得有关哪些变量处于活动状态的准确信息。
这种重写允许 1.4 中的垃圾收集器完全精确,这意味着它知道程序中所有活动指针的位置。这意味着堆会更小,因为不会有误报使非指针保持活动状态。其他相关更改也减少了堆大小,相对于之前的版本,总体上减小了 10%-30%。
结果是堆栈不再分段,消除了“热拆分”问题。当达到堆栈限制时,分配一个新的更大的堆栈,goroutine 的所有活动帧都被复制到那里,并且任何指向堆栈的指针都会被更新。
初步答案(2014 年 3 月)
Agis Anastasopoulo的文章“ Go 中的连续堆栈”也解决了这个问题
在堆栈边界恰好落在紧密循环中的这种情况下,重复创建和销毁段的开销变得很大。
这被称为 Go 社区内部的“热分裂”问题。
“热拆分”将在 Go 1.3 中通过实现连续堆栈来解决。
现在,当堆栈需要增长时,会发生以下情况,而不是分配新段:
- 创建一个更大的新堆栈
- 将旧堆栈的内容复制到新堆栈
- 重新调整每个复制的指针以指向新地址
- 销毁旧堆栈
下面提到一个主要出现在 32 位架构中的问题:
不过有一定的挑战。
1.2 运行时不知道堆栈中指针大小的字是否是实际指针。可能有浮点数和最罕见的整数,如果被解释为指针,实际上会指向数据。
由于缺乏此类知识,垃圾收集器必须保守地将堆栈帧中的所有位置视为根。这留下了内存泄漏的可能性,尤其是在 32 位架构上,因为它们的地址池要小得多。
但是,在复制堆栈时,必须避免这种情况,并且在重新调整时只应考虑真正的指针。
虽然工作已经完成,关于实时堆栈指针的信息现在嵌入到二进制文件中,并且可供运行时使用。
这意味着不仅 1.3 中的收集器可以精确地堆栈数据,而且现在可以重新调整堆栈指针。