什么是更有效的堆栈内存或堆? [复制]

Posted

技术标签:

【中文标题】什么是更有效的堆栈内存或堆? [复制]【英文标题】:What is more efficient stack memory or heap? [duplicate] 【发布时间】:2011-06-25 20:49:15 【问题描述】:

可能重复:C++ Which is faster: Stack allocation or Heap allocation

从内存分配的角度来看,堆栈内存或堆内存哪个更有效?这取决于什么?

显然,动态分配与堆栈分配相比存在开销。使用堆涉及找到可以分配内存的位置和维护结构。在堆栈上很简单,因为您已经知道将元素放在哪里。我想了解在最坏情况下允许动态分配的支持结构的开销(以毫秒为单位)是多少?

【问题讨论】:

有什么效率? 投票结束作为 NARQ。 显而易见的答案是堆栈,但堆栈本身控制着您分配的任何东西的生命周期(可能非常有限)。 我认为这是一个非常合理的问题。如果您还不确定答案是什么(这取决于您在做什么),就没有办法提出更细致入微的问题。我认为我们应该重新打开它。 这是个错误的问题。在 99% 的情况下,这种差异不会对您产生任何影响。动态或自动的问题应仅根据其用法来完成。在 1% 的情况下,您知道这很重要意味着您知道如何做适当的事情。 【参考方案1】:

堆栈通常在速度方面更高效,并且易于实现!

我倾向于同意来自 Joel on Software 网站的 Michael 的观点,他说,

使用栈效率更高 可能的时候。

当你从堆中分配时, 堆管理器必须经历什么 有时相对复杂 过程,找到一个空闲的块 记忆。有时不得不看 稍微找点东西 大小合适。

这通常不是一个可怕的数量 开销,但肯定更多 与堆栈相比如何复杂的工作 职能。当您使用内存时 堆栈,编译器能够 立即申请一块内存 从堆栈中使用。它是 基本上是一个更简单的过程。

但是,堆栈的大小是 有限,所以你不应该使用它 非常大的东西,如果你需要 大于 像4k左右,然后你 应该总是从堆中抓取它 而是。

使用堆栈的另一个好处是 它会自动清理 当当前函数退出时,你 不必担心清洁它 你自己。你必须更多 小心堆分配 确保它们被清理干净。使用 处理的智能指针 自动删除堆 分配可以对此有很大帮助。

当我看到这样的代码时,我有点讨厌它 做一些事情,比如分配 2 个整数 从堆因为程序员 需要一个指向 2 个整数的指针和 当他们看到指针时,他们只是 自动假设他们需要 使用堆。我倾向于看到这个 经验不足的编码员有点 - 这是你喜欢的东西 应该使用堆栈并且只拥有 上声明的 2 个整数的数组 堆栈。

引自 Joel on Software 网站上的一次非常好的讨论:

stack versus heap: more efficiency?

【讨论】:

【参考方案2】:

在堆栈上分配/释放更“有效”,因为它通常只涉及增加/减少堆栈指针,而堆分配通常要复杂得多。也就是说,在堆栈上放置大量内容通常不是一个好主意,因为在大多数系统上堆栈空间远比堆空间有限(尤其是在涉及多个线程时,因为每个线程都有一个单独的堆栈)。

【讨论】:

为什么通过多个堆栈拥有更多堆栈空间意味着堆栈空间“特别”有限? @Fred 每个线程的堆栈空间更加有限。在单线程系统上,您可以让堆栈从内存的一端开始,让堆从另一端“增长”,因此理论上您可以将整个虚拟地址空间用于堆栈(如果您不使用堆)。一旦有了线程,每个线程都需要自己的堆栈。通常的做法是为每个堆栈分配一个相对较小的地址空间切片。您期望的线程越多,每个切片必须越小。这在 64 位机器上可能不是问题,因为它们有更多的地址空间。 我可能应该补充一点,我假设您使用的是具有“平面”地址空间的系统。如果您使用分段指针,您可以让每个线程的堆栈位于不同的段上。此外,一些语言/运行时对堆栈做了一些花哨的事情,因此它们可以扩展重定位。我相信 Go 实际上可能会这样做,尽管我看不出如何在 C++(或 C)中做到这一点而不破坏指向堆栈的指针的语义。 使用 64 位地址空间,您可以为相当大的每线程堆栈留出空间,根据需要一次在实际内存中映射一页。话虽如此,人们用完堆栈空间的通常原因是不受控制的递归,而不是真正需要更多内存。 @Steven:是的,我在上面的评论中提到这在 64 位机器上不是问题。不受控制的递归当然是人们用完堆栈的原因之一,尽管我不知道这是否是“通常的原因”。似乎大多数 C 和 C++ 程序员实际上已经深入研究了“递归效率低下和/或不好”,而真正的问题之一是你不能用你通常拥有的那种堆栈进行非常深的递归支持多线程的 32 位平台。当 1MB 的堆栈变成 4GB 的堆栈时,权衡会有些不同。【参考方案3】:

这两个内存区域针对不同的用例进行了优化。

堆栈针对以 FIFO 顺序释放对象的情况进行了优化 - 即,总是在较旧的对象之前分配较新的对象。因此,只需维护一个巨大的字节数组,然后在最后移交或收回字节,就可以快速分配和释放内存。因为存储函数调用的局部变量所需的内存总是以这种方式回收(因为函数总是以它们被调用的相反顺序完成执行),堆栈是分配这种内存的好地方。

但是,堆栈不擅长进行其他类型的分配。您不能轻易地释放从不是最近分配的块的堆栈中分配的内存,因为这会导致堆栈中的“间隙”并使确定字节可用位置的逻辑复杂化。对于这些类型的分配,对象的生命周期不能从分配对象的时间来确定,堆是存储东西的更好地方。实现堆的方法有很多,但大多数都依赖于存储一个巨大的表或块的链表的想法,这些块的分配方式很容易找到合适的内存块以交回给客户端。当内存被释放时,它会被重新添加到表或链表中,并且可能会应用一些其他逻辑来将块与其他块压缩。由于搜索时间的这种开销,堆通常比堆栈慢得多。但是,堆可以按照堆栈通常不擅长的模式进行分配,因此两者通常都存在于程序中。

有趣的是,还有一些其他分配内存的方法介于两者之间。一种常见的分配技术使用称为“竞技场”的东西,其中从堆中分配了一大块内存,然后将其划分为更小的块,就像在堆栈中一样。这带来的好处是,如果分配是顺序的(例如,如果您要分配许多都存在相同长度的小对象),则来自 arena 的分配非常快,但是这些对象的寿命可能比任何特定的函数调用都长.存在许多其他方法,这只是可能的一小部分示例,但应该清楚的是,内存分配都是关于权衡的。您只需要找到适合您特定需求的分配器。

【讨论】:

【参考方案4】:

堆栈效率更高,但大小有限。我认为它类似于 1MByte。

在堆上分配内存时,我会记住数字 1000。在堆上分配比在堆栈上慢 1000 倍。

【讨论】:

堆栈大小取决于您的平台和您的应用程序。如果您创建一个工作线程,您通常甚至可以定义堆栈大小。绝对没有像“1MB”这样的神奇数字。 1000 的因数从何而来? @Leonid:我不知道。它会因系统而异,坦率地说,我从来没有计时过。 @Leonid,1000 次可能是夸大了,但是:它至少需要一个 CAS(也可能是缓存未命中),新内存的缓存未命中很少,缓存未命中就像300cpu时钟。堆栈分配是方法体所必需的单个 CPU 指令,并且堆栈很可能位于 CPU 缓存中。 @Stephane Rolland Windows 的堆栈大小过去在某些时候默认为 1MB(现在仍然是?),但没有通用数字。

以上是关于什么是更有效的堆栈内存或堆? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

是否在C中的堆栈或堆上创建激活记录?

WebAssembly:复制栈顶

存储在堆栈或堆中的对象方法?

为啥要在堆上而不是栈上分配内存? [复制]

java堆栈堆栈的区别

关于JAVA堆栈的简单说明