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

Posted

技术标签:

【中文标题】Linux中的堆栈内存在物理上是连续的吗?【英文标题】:Is stack memory contiguous physically in Linux? 【发布时间】:2018-09-10 17:02:18 【问题描述】:

据我所知,堆栈内存在虚拟内存地址上是连续的,但堆栈内存在物理上也是连续的?这与堆栈大小限制有关吗?

编辑:

我曾经认为堆栈内存在物理上不一定是连续的,但为什么我们认为堆栈内存总是比堆内存快?如果它在物理上不是连续的,堆栈如何更好地利用缓存?还有一件事总是让我感到困惑,cpu在数据段中执行指令,它不在虚拟内存中的堆栈段附近,我认为操作系统不会使堆栈段和数据段在物理上彼此靠近,所以这可能会损害缓存效果,您怎么看?

再次编辑: 也许我应该举个例子来更好地表达自己,如果我们要对大量数字进行排序,使用数组来存储数字比使用列表更好,因为每个列表节点都可能由malloc构造,所以它可能没有好好利用缓存,所以我说栈内存比堆内存快。

【问题讨论】:

我们有虚拟内存,不用关心物理内存布局。如果您不编写内核或驱动程序(或设计硬件,或设计对缓存的攻击),请忘记物理内存。 Nah - 进程/线程堆栈可以并且经常像任何其他虚拟内存一样被分页。用于中断处理的内核堆栈必须是非分页的。 Is stack memory contiguous?、Virtually contiguous vs. physically contiguous memory、Contiguous physical memory from userspace 等可能重复 @jww 我已经编辑了我的问题以显示我的真正困惑,如果您得到答案,请在下面发布您的答案或添加评论,非常感谢。 在大多数架构上,堆栈都可以通过简单的算术指令来增长或缩小。 malloc 是对一个函数的调用,该函数需要保留一个内存区域,最终分配新页面。那要花很多钱。简单来说,栈是一种限制性更强的数据结构。缓存与它关系不大,栈顶在缓存中几乎总是热的,因为它使用非常频繁,但很容易构造一个将其踢出缓存的函数。 【参考方案1】:

据我所知,堆栈内存在虚拟内存中是连续的 地址,但堆栈内存在物理上也是连续的?并这样做 与堆栈大小限制有关吗?

不,堆栈内存在物理地址空间中不一定是连续的。它与堆栈大小限制无关。这与操作系统如何管理内存有关。操作系统仅在第一次访问相应的虚拟页面(或自从它被分页到磁盘后第一次访问)时才分配一个物理页面。这称为demand-paging,它有助于节省内存使用量。

为什么我们认为堆栈内存总是更快 比堆内存?如果它不是物理上连续的,如何堆叠 充分利用缓存?

它与缓存无关。从堆栈分配和释放内存比堆更快。这是因为从堆栈分配和释放只需要一条指令(递增或递减堆栈指针)。另一方面,从堆中分配和/或解除分配内存涉及更多工作。有关详细信息,请参阅this 文章。

现在,一旦分配了内存(从堆或堆栈),访问分配的内存区域所需的时间取决于它是堆栈内存还是堆内存。这取决于内存访问行为以及是否是friendly 到缓存和内存架构。

如果我们要对大量的数字进行排序,使用数组来存储 数字比使用列表更好,因为每个列表节点都可能是 由 malloc 构造,因此它可能无法很好地利用缓存, 这就是为什么我说堆栈内存比堆内存快。

使用数组更快不是因为数组是从堆栈中分配的。可以从任何内存(堆栈、堆或任何地方)分配数组。它更快,因为数组通常一次连续访问一个元素。当第一个元素被访问时,包含该元素和其他元素的整个高速缓存行从内存中提取到 L1 高速缓存。因此访问该缓存行中的其他元素可以非常有效地完成,但访问缓存行中的第一个元素仍然很慢(除非缓存行是prefetched)。这是关键部分:因为缓存行是 64 字节对齐的,并且虚拟页面和物理页面也是 64 字节对齐的,所以可以保证任何缓存行完全驻留在单个虚拟页面和单个物理页面中。这使得获取缓存行变得高效。同样,所有这些都与数组是从堆栈还是堆中分配的无关。无论哪种方式都是正确的。

另一方面,由于链表的元素通常不连续(甚至在虚拟地址空间中也不连续),因此包含一个元素的缓存行可能不包含任何其他元素。所以获取每一个元素可能会更昂贵。

【讨论】:

还有一个问题:为什么Linux中的堆栈内存限制为8M? @cong 这样堆栈溢出只会使有问题的应用程序崩溃,而不会消耗系统的所有内存,这可能会使整个系统本身崩溃。通常,不希望应用程序同时从堆栈分配大量内存。大对象应该从堆中分配。【参考方案2】:

记忆就是记忆。堆栈内存不比堆内存快,也不慢。都是一样的。使内存成为堆栈或堆的唯一因素是应用程序如何分配它。完全可以在堆上分配一块内存,把它变成程序栈。

速度差异在于分配。通过从堆栈指针中减去一条指令来分配堆栈内存。

分配堆的过程取决于堆管理器,但它要复杂得多,可能需要将页面映射到地址空间。

【讨论】:

1.也许我应该举个例子更好地表达自己,如果我们要对大量数字进行排序,使用数组存储数字比使用列表更好,因为每个列表节点都可能由malloc构造,所以可能不需要缓存的优势,这就是为什么我说堆栈内存比堆内存快。 2.it is much more complex and may requiring mapping pages to the address space,至于栈,我认为栈内存在页表中肯定有条目,那么栈内存什么时候映射到地址空间。 当您想猜测缓存时,您的编程就会遇到麻烦。内存就是内存,因此 CPU 中的缓存管理器甚至无法区分堆和缓存。【参考方案3】:

不,不保证物理地址的连续性。不过没关系,因为用户空间程序不使用物理地址,所以不知道是不是这样。

【讨论】:

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

这是一个复杂的话题。

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

堆栈可能会更快,因为它经常被使用。当你调用一个函数时,参数和局部变量被放入堆栈,所以缓存是新鲜的。另外,由于函数调用和返回的频率很高,可能在其他缓存级别中还有一些堆栈,并且很少对堆栈顶部进行分页(因为它是最近使用的)。

所以缓存可能会更快,但前提是您的变量很少。如果您允许堆栈上的大型数组,例如用alloca,优势就消失了。

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

【讨论】:

以上是关于Linux中的堆栈内存在物理上是连续的吗?的主要内容,如果未能解决你的问题,请参考以下文章

kmalloc 分配实际上不是连续的吗?

iOS进程内存分配(页、栈、堆)

Linux内存-伙伴系统

JVM基础和内存区域剖析

linux内存池能分配连续物理内存吗

在C语言中,如何给函数分配内存?