为了账号安全,请及时绑定邮箱和手机立即绑定

在 amd64 上拆分堆栈是不必要的

在 amd64 上拆分堆栈是不必要的

Go
元芳怎么了 2021-06-29 10:55:05
似乎有一种观点认为在 64 位体系结构上不需要使用“拆分堆栈”运行时模型。我说似乎是,因为我还没有看到有人真的这么说,只能绕着它跳舞:典型的多线程程序的内存使用量可以显着减少,因为每个线程不需要最坏情况下的堆栈大小。在 32 位地址空间中运行数百万个线程(完整的 NPTL 线程或协同例程)成为可能。——伊恩·兰斯·泰勒...暗示 64 位地址空间已经可以处理它。和......拆分堆栈的持续开销和狭窄的用例(在 32 位体系结构上产生大量 I/O 绑定任务)是不可接受的...--bstrie两个问题:这是他们所说的吗?其次,如果是这样,为什么它们在 64 位架构上是不必要的?
查看完整描述

3 回答

?
HUH函数

TA贡献1836条经验 获得超4个赞

是的,他们就是这么说的。

在 64 位架构上(目前)不需要拆分堆栈,因为 64 位虚拟地址空间非常大,可以包含数百万个堆栈地址范围,如果需要,每个地址范围都相当于整个 32 位地址空间。

在当今使用的平面内存模型中,从虚拟地址到物理内存位置的转换是在硬件 MMU的支持下完成的。在amd64 上,事实证明将 64 位虚拟地址空间的大块保留给您正在创建的每个新堆栈会更好(意味着总体上更快),而只将第一页 (4kB) 映射到实际 RAM。这样,堆栈将能够根据需要在连续的虚拟地址上增长和缩小(意味着每个函数序言中的代码更少,一个很大的优化),而操作系统重新配置 MMU 以将虚拟地址的每个页面映射到实际RAM 的空闲页面,每当堆栈增长或缩小到高于/低于某些可配置阈值时。

通过巧妙地选择阈值(例如参见动态数组理论),您可以在平均堆栈操作上实现 O(1) 复杂度,同时保留数百万堆栈的好处,这些堆栈可以根据需要增长并且只消耗内存他们使用。

PS:当前的 Go 实现远远落后于此:-)


查看完整回答
反对 回复 2021-07-05
?
慕桂英546537

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 中的收集器可以精确地堆叠数据,而且现在可以重新调整堆栈指针。


查看完整回答
反对 回复 2021-07-05
  • 3 回答
  • 0 关注
  • 207 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信