3 回答
TA贡献1836条经验 获得超4个赞
是的,他们就是这么说的。
在 64 位架构上(目前)不需要拆分堆栈,因为 64 位虚拟地址空间非常大,可以包含数百万个堆栈地址范围,如果需要,每个地址范围都相当于整个 32 位地址空间。
在当今使用的平面内存模型中,从虚拟地址到物理内存位置的转换是在硬件 MMU的支持下完成的。在amd64 上,事实证明将 64 位虚拟地址空间的大块保留给您正在创建的每个新堆栈会更好(意味着总体上更快),而只将第一页 (4kB) 映射到实际 RAM。这样,堆栈将能够根据需要在连续的虚拟地址上增长和缩小(意味着每个函数序言中的代码更少,一个很大的优化),而操作系统重新配置 MMU 以将虚拟地址的每个页面映射到实际RAM 的空闲页面,每当堆栈增长或缩小到高于/低于某些可配置阈值时。
通过巧妙地选择阈值(例如参见动态数组理论),您可以在平均堆栈操作上实现 O(1) 复杂度,同时保留数百万堆栈的好处,这些堆栈可以根据需要增长并且只消耗内存他们使用。
PS:当前的 Go 实现远远落后于此:-)
TA贡献1848条经验 获得超10个赞
更新 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 中的收集器可以精确地堆叠数据,而且现在可以重新调整堆栈指针。
- 3 回答
- 0 关注
- 207 浏览
添加回答
举报