在Linux中,堆栈内存是物理上连续的吗?

8
据我所知,栈内存在虚拟内存地址中是连续的,但物理上的栈内存也是连续的吗?这是否与栈大小限制有关?
编辑:我曾经认为栈内存不必在物理上是连续的,但我们为什么认为栈内存总是比堆内存快呢?如果它在物理上不是连续的,那么栈如何更好地利用缓存?还有一件事情总是让我感到困惑,CPU在数据段中执行指令,而数据段在虚拟内存中并不靠近栈段,我不认为操作系统会使栈段和数据段在物理上靠得很近,因此这可能会损害缓存效果,你怎么看?
再次编辑:也许我应该举个例子来更好地表达自己,如果我们想要对大量数字进行排序,使用数组来存储数字比使用链表更好,因为每个链表节点都可能由malloc构造,所以它可能无法充分利用缓存,这就是我说栈内存比堆内存更快的原因。

2
我们有虚拟内存,不必关心物理内存布局。如果你不是在编写内核或驱动程序(或设计硬件,或设计攻击缓存),就不要考虑物理内存。 - Giacomo Catenazzi
2
不是的 - 进程/线程堆栈可以像任何其他虚拟内存一样进行分页。用于中断处理的内核堆栈必须是非分页的。 - Martin James
@MartinJames 我已经编辑了我的问题以展示我的真正困惑,如果你有答案,请在下面发布你的答案或添加评论,非常感谢。 - cong
1
在大多数架构上,堆栈可以通过简单的算术指令进行扩展或缩小。malloc是调用需要记录内存区域并最终分配新页面的函数。这很费钱。简而言之,堆栈是一种更为限制性的数据结构。缓存与此关系不大,堆栈顶部几乎总是在缓存中非常热门,因为它被非常频繁地使用,但很容易构建一个将其从缓存中清除的函数。 - Margaret Bloom
显示剩余2条评论
4个回答

10
据我所知,堆栈内存在虚拟内存地址中是连续的,但物理上并不一定连续。这与堆栈大小限制无关,而与操作系统如何管理内存有关。操作系统只有在第一次访问相应的虚拟页面时(或自从它被分页到磁盘以来的第一次访问),才分配物理页面。这被称为需求分页,有助于节省内存使用。
我们为什么认为堆栈内存总是比堆内存快?如果它不是物理上连续的,那么堆栈怎么能更好地利用缓存呢?这与缓存无关。从堆栈中分配和释放内存只需要一条指令(增加或减少堆栈指针),因此比从堆中分配和/或释放内存要快得多。现在,一旦内存被分配(从堆或堆栈中),访问该分配的内存区域所需的时间并不取决于它是堆栈还是堆内存,而取决于内存访问行为以及是否友好于缓存和内存架构。请参见文章获取更多信息。
如果我们想要对大量数字进行排序,使用数组存储数字比使用链表更好,因为每个链表节点可能是由malloc构建的,所以它可能无法充分利用缓存,这就是我说栈内存比堆内存更快的原因。
使用数组更快的原因不在于数组是从堆栈分配的。数组可以从任何内存(堆栈、堆或任何地方)中分配。它更快是因为通常一个接一个地连续访问数组元素。当第一个元素被访问时,包含该元素和其他元素的整个缓存行从内存中获取到L1缓存。因此,访问该缓存行中的其他元素可以非常有效地完成,但访问该缓存行中的第一个元素仍然很慢(除非该缓存行被预取)。这是关键部分:由于缓存行是64字节对齐的,并且虚拟和物理页面也是64字节对齐的,因此可保证任何缓存行完全驻留在单个虚拟页和单个物理页中。这就是使提取缓存行高效的原因。同样,所有这些都与数组是从堆栈还是堆中分配无关。不管哪种方式,都是适用的。
另一方面,由于链表的元素通常不连续(甚至在虚拟地址空间中也不是连续的),因此包含元素的缓存行可能不包含任何其他元素。因此,提取每个单独的元素可能更加昂贵。

还有一个问题:为什么在Linux中堆栈内存被限制在大约8M? - cong
为了使堆栈溢出仅崩溃有问题的应用程序,而不会消耗系统的所有内存,从而可能导致整个系统崩溃。通常,不希望应用程序同时从堆栈中分配大量内存。大型对象应该从堆中分配。 - Hadi Brais

3
记忆是记忆,堆栈内存并不比堆内存更快,也不比堆内存更慢。它们都是一样的。唯一使内存成为堆或者栈的原因是应用程序如何进行分配。完全可以在堆上分配内存并将其作为程序堆栈使用。
速度差异在于分配方式。栈内存是通过减去栈指针来进行分配的:只需一个指令。
堆的分配过程取决于堆管理器,但它要复杂得多,可能需要将页面映射到地址空间中。

  1. 或许我应该举个例子来更好地表达自己,如果我们想要对大量数字进行排序,使用数组存储数字比使用列表更好,因为每个列表节点可能是由malloc构造的,所以它可能无法充分利用缓存,这就是为什么我说栈内存比堆内存更快的原因。
  2. “它要复杂得多,可能需要将页面映射到地址空间”,所以对于栈来说,我认为栈内存必须在页表中有条目,那么何时会将页面映射到栈内存的地址空间中呢?
- cong
当你开始试图猜测缓存时,编程就变得困难了。内存就是内存,所以CPU中的缓存管理器甚至无法区分堆和缓存之间的差异。 - user3344003

2

不,物理地址之间没有保证相邻的承诺。但这并不重要,因为用户空间程序不使用物理地址,所以不知道这是怎么回事。


我已经编辑了我的问题以展示我的真正困惑,如果您得到了答案,请更新您的答案,非常感谢。 - cong
1
@cong:一个L1缓存行通常是64字节,但单个VM页面通常是4096字节。因此,认为堆栈性能会因物理内存连续性而增加的想法是站不住脚的,因为在一个缓存行的级别上,内存始终是物理连续的。我认为你的大多数新问题可以回答为“不,它不是这样工作的”,你需要开始阅读关于现代处理器中缓存如何工作的内容。 - John Zwinck
那么,为什么我们总是说堆栈内存比堆内存更快?我认为这一定与高速缓存有关,是否因为堆内存在物理上不是连续的? - cong
我知道堆和栈的内存访问本质相同,我可以举个例子更好地表达自己。如果我们想要对大量数据进行排序,使用数组来存储数据比使用链表更好,因为每个链表节点可能是通过malloc构造的,所以它可能无法充分利用缓存,这就是为什么我说栈内存比堆内存更快的原因。 - cong
1
@cong:在使用上,堆内存并不比栈内存更快。只有在分配方面才更快。在栈上分配只需要增加栈指针。而在堆上分配,在最坏的情况下需要进行系统调用(sbrkmmap)。 - John Zwinck

1

这是一个复杂的主题。

堆和栈通常具有相同的内存和内存类型(MTRR,每个页面的缓存设置等)。[mmap、文件、驱动程序可能具有不同的策略,或者当用户明确更改时]。

栈可能更快,因为它经常被使用。当调用函数时,参数和局部变量被放入栈中,因此缓存是新鲜的。此外,由于函数经常调用和返回,很可能在其他缓存级别中有一些更多的栈,并且很少将栈顶分页(因为它最近被使用)。

所以,缓存可能更快,但只有在您拥有少量变量的情况下才是如此。如果允许在堆栈上使用大型数组,例如使用 alloca,则优势会消失。

总的来说,这是一个非常复杂的话题,最好不要过度优化,因为这可能会导致复杂的代码,从而更难重构和高级优化代码。(例如,在多维数组上,索引(和因此内存)的顺序和循环可以明显改善速度,但也很快代码将变得难以维护)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接