对于一般的系统来说,废除堆栈而仅使用堆进行内存管理会更有效吗?


14

在我看来,可以用堆栈完成的所有事情都可以用堆来完成,但是不能用堆栈完成的所有事情都可以用堆栈来完成。那是对的吗?然后,为了简单起见,即使我们在某些工作负载下确实损失了少量性能,仅采用一个标准(即堆)难道不是更好吗?

考虑一下模块化和性能之间的权衡。我知道这不是描述这种情况的最佳方法,但是总的来说,即使有可能获得更好的性能,理解和设计的简单性似乎也是一个更好的选择。


1
在C和C ++中,您需要显式取消分配在堆上分配的内存。那并不简单。
user16764 2012年

我使用了一个C#实现,剖析显示堆栈对象被分配到堆状区域,并且垃圾回收非常糟糕。我的解决方案?将所有可能的内容(例如循环变量,临时变量等)移到持久性堆内存中。使程序吃掉RAM的10倍,并以10倍的速度运行。
imallett 2014年

@IanMallett:我不理解您对问题和解决方案的解释。您是否在某个地方有更多信息的链接?通常,我发现基于堆栈的分配速度更快。
Frank Hileman 2014年

@FrankHileman的基本问题是:我正在使用的C#的实现的垃圾回收速度非常差。解决方案是使所有变量持久化,以便在运行时不进行任何内存操作。不久前,我写了一篇有关C#/ XNA开发的评论文章,其中还讨论了一些上下文。
imallett 2014年

@IanMallett:谢谢。作为现今主要使用C#的前C / C ++开发人员,我的经历与众不同。我发现图书馆是最大的问题。听起来XBox360平台对于.net开发人员是半生半熟。通常,当我遇到GC问题时,我会切换到缓冲池。确实有帮助。
Frank Hileman 2014年

Answers:


30

堆不利于快速分配和释放内存。如果您想在有限的时间内获取少量内存,那么堆并不是您的最佳选择。具有超简单分配/释放算法的堆栈自然在此方面表现出色(如果将其内置到硬件中则更是如此),这就是为什么人们将其用于诸如将参数传递给函数和存储局部变量之类的原因。重要的缺点是它的空间有限,因此在其中保留大型对象或尝试将其用于寿命长的对象都是不好的主意。

为了简化编程语言而完全摆脱堆栈是IMO的错误方法-更好的方法是将差异抽象化,让编译器找出要使用的存储类型,而程序员将更高的存储在一起-与人类思维方式更接近的层次结构-实际上,诸如C#,Java,Python等高级语言正是这样做的。它们为堆分配的对象和堆栈分配的原语(.NET术语中的“引用类型”与“值类型”)提供几乎完全相同的语法,这些语法完全透明,或者您必须理解一些功能差异才能使用该语言。正确(但是您实际上不必知道堆栈和堆在内部如何工作)。


2
真是太好了:)对初学者来说非常简洁和有用!
黑暗圣堂武士

1
在许多CPU上,堆栈是用硬件处理的,这是语言之外的问题,但在运行时起着很大的作用。
Patrick Hughes

@帕特里克·休斯:是的,但是堆也位于硬件中,不是吗?
黑暗圣堂武士

@Dark Patrick可能想说的是x86之类的体系结构具有特殊的寄存器来管理堆栈,并具有特殊的指令来将某些东西放进堆栈或从堆栈中删除。这使得它非常快。
FUZxxl

3
@同胞们:都是如此。但是关键是堆栈和堆都有其优缺点,因此相应地使用它们将产生最有效的代码。
tdammers,2011年

8

简而言之,堆栈并不是一点点性能。它比堆快数百或数千倍。另外,大多数现代机器都具有对堆栈的硬件支持(例如x86),并且不能删除例如调用堆栈的硬件功能。


当您说现代机器对堆栈具有硬件支持时,您是什么意思?堆栈本身已经在硬件中了,不是吗?
黑暗圣堂武士

1
x86具有用于处理堆栈的特殊寄存器和指令。x86不支持堆-这些东西是由OS创建的。
2011年

8

没有

相比较而言,C ++中的堆栈区域快得令人难以置信。我敢冒险,没有经验的C ++开发人员会愿意禁用该功能。

使用C ++,您可以选择并拥有控制权。设计师并不特别倾向于引入会增加执行时间或空间的功能。

行使选择

如果要构建要求动态分配每个对象的库或程序,则可以使用C ++进行。它的执行速度相对较慢,但是您可以拥有这种“模块化”。对于我们其他人来说,模块化始终是可选的,请根据需要对其进行介绍,因为良好/快速的实现都需要模块化。

备择方案

还有其他语言要求在堆上创建每个对象的存储。它非常慢,以至于损害设计(现实世界的程序)的方式比必须学习两者(IMO)更糟糕。

两者都很重要,C ++可以有效地为每种给定场景提供电源。话虽如此,但如果OP中的这些因素对您很重要(例如,请阅读高级语言),则C ++语言可能不是您设计的理想选择。


堆实际上与堆栈具有相同的速度,但是没有专门的硬件支持分配。另一方面,有很多方法可以大大加快堆的使用(受多种条件的限制,这些条件使它们成为专家专用技术)。
多纳研究员

@DonalFellows:堆栈的硬件支持无关紧要。重要的是要知道,无论何时释放任何内容,都可以释放它之后分配的任何内容。某些编程语言没有可以独立释放对象的堆,而只具有“释放之后分配的所有内容”方法。
超级猫

6

然后,为了简单起见,即使我们在某些工作负载下确实损失了少量性能,仅采用一个标准(即堆)难道不是更好吗?

实际上,性能损失可能很大!

正如其他人指出的那样,堆栈是一种非常有效的结构,用于管理遵守LIFO(后进先出)规则的数据。堆栈上的内存分配/释放通常只是对CPU上的寄存器的更改。更改寄存器几乎始终是处理器可以执行的最快的操作之一。

堆通常是一个相当复杂的数据结构,分配/释放内存将花费很多指令来完成所有相关的簿记工作。更糟糕的是,在常见的实现中,每次使用堆的调用都可能导致对操作系统的调用。操作系统调用非常耗时!该程序通常必须从用户模式切换到内核模式,并且每当发生这种情况时,操作系统可能会决定其他程序有更紧迫的需求,并且您的程序将需要等待。


5

Simula将堆用于所有内容。将所有内容放到堆上总是会引起局部变量的间接访问,这给垃圾收集器带来了额外的压力(您必须考虑到垃圾收集器当时确实很烂)。这就是Bjarne发明C ++的部分原因。


因此,基本上C ++也只使用堆吗?
黑暗圣堂武士

2
@暗:什么?不会。Simula中缺少堆栈是启发它做得更好。
fredoverflow

啊,我明白你的意思了!谢谢+1 :)
黑暗圣堂武士

3

堆栈对于LIFO数据(例如与函数调用关联的元数据)非常有效。堆栈还利用了CPU的固有设计功能。由于此级别的性能对于流程中的所有其他方面都是至关重要的,因此,对该级别的“小”打击将非常广泛地传播。另外,堆内存可被操作系统移动,这对堆栈来说是致命的。虽然可以在堆中实现堆栈,但它需要开销,这将在最细粒度的级别上影响字面上的每个进程。


2

就您编写代码而言,“高效”可能会,但就软件效率而言,肯定不会。堆栈分配基本上是免费的(只需几条机器指令即可移动堆栈指针并在堆栈上为局部变量保留空间)。

由于堆栈分配几乎不需要时间,因此即使在非常高效的堆上进行分配也要慢100k倍(如果不是1M +)。

现在,想象一个典型应用程序使用多少个局部变量和其他数据结构。用作循环计数器的每个小“ i”的分配速度要慢一百万倍。

确保硬件速度足够快,您可以编写仅使用堆的应用程序。但是现在想像一下,如果您利用堆并使用相同的硬件,则可以编写什么样的应用程序。


当您说“想象一个典型的应用程序使用多少个局部变量和其他数据结构”时,您具体指的是什么其他数据结构?
黑暗圣堂武士

1
值“ 100k”和“ 1M +”是否科学?还是只是说“很多”的一种方式?
布鲁诺·里斯

@Bruno-恕我直言,我使用的100K和1M数字实际上是保守的估计,以证明这一点。如果您熟悉VS和C ++,请编写一个在堆栈上分配100字节的程序,并编写一个在堆上分配100字节的程序。然后切换到反汇编视图,并简单地计算每次分配占用的汇编指令数。堆操作通常是对Windows DLL的几个函数调用,先是桶和链表,然后是合并和其他算法。使用堆栈,它可以简化为一条汇编指令“ add esp,100” ...
DXM

2
“慢100k(如果不是1M +)”?这有点夸张了。让它慢两个数量级,也许三个,但是仅此而已。至少,我的Linux在核心i5上能够在不到6秒的时间内完成100M堆分配(+一些周围的指令),每个分配不能超过数百条指令-实际上,几乎可以肯定要少得多。如果它比堆栈慢六个数量级,则操作系统的堆实现存在严重错误。当然,Windows确实有很多问题,但是……
大约

1
版主可能会杀死整个评论线程。因此,这是笔交易,我承认实际数字已从我的..中撤出,但是我们同意这个因素确实非常重要,并且不作更多评论:)
DXM


1

调用堆栈如何在堆上工作?本质上,您将必须在每个程序的堆上分配一个堆栈,那么为什么不让OS +硬件为您这样做呢?

如果您希望事情真的非常简单和高效,只需给用户分配他们的内存块,然后让他们处理。当然,没有人愿意自己实现一切,这就是为什么我们有一个堆栈和一个堆。


严格来说,“调用栈”不是编程语言运行时环境的必需功能。例如,通过图约简(我已经编码)对延迟评估的函数语言的直接实现没有调用栈。但是,调用栈是一种非常有用且广泛使用的技术,尤其是当现代处理器假定您正在使用它并且已对其使用进行了优化时。

@Ben-虽然从某种语言中抽象出诸如内存分配之类的东西是正确的(也是一件好事),但这并没有改变现在流行的计算机体系结构。因此,您的图归约代码在运行时仍会使用堆栈(无论是否喜欢)。
戈(Ingo)

@Ingo真的没有任何有意义的意义。当然,操作系统将初始化传统上称为“堆栈”的一部分内存,并且会有一个指向它的寄存器。但是源语言中的函数不会按照调用顺序显示为堆栈框架。函数执行完全由对堆中数据结构的操作表示。即使不使用最后调用优化,也无法“溢出堆栈”。这就是我说“调用堆栈”没有任何基础的意思。

我不是在说源语言的功能,而是在解释器(或其他)中实际执行图形约简的功能。这些将需要一个堆栈。这很明显,因为现代硬件无法进行图形还原。因此,您的图归约算法最终会映射到机器ode,我敢打赌它们之间有子例程调用。QED。
Ingo

1

堆栈和堆都是必需的。它们用于不同的情况,例如:

  1. 堆分配具有以下限制:sizeof(a [0])== sizeof(a [1])
  2. 堆栈分配有一个限制,即sizeof(a)是编译时常数
  3. 堆分配可以执行循环,图形等复杂的数据结构
  4. 堆栈分配可以生成编译时大小的树
  5. 堆需要跟踪所有权
  6. 堆栈分配和重新分配是自动的
  7. 堆内存可以通过指针轻松地从一个范围传递到另一个范围
  8. 堆栈内存是每个函数的本地对象,需要将对象移到上限以延长其寿命(或存储在对象内部而不是内部成员函数中)
  9. 堆对性能不利
  10. 堆栈非常快
  11. 堆对象通过获取所有权的指针从函数返回。或shared_ptrs。
  12. 堆栈对象是通过不具有所有权的引用从函数返回的。
  13. 堆要求将每个新内容与正确的delete或delete []类型进行匹配
  14. 堆栈对象使用RAII和构造函数初始化列表
  15. 堆对象可以在函数内的任何点初始化,并且不能使用构造函数参数
  16. 堆栈对象使用构造函数参数进行初始化
  17. 堆使用数组,并且数组大小可以在运行时更改
  18. 堆栈用于单个对象,并且大小在编译时是固定的

基本上根本无法比较这些机制,因为很多细节都不同。它们唯一的共同之处在于它们都以某种方式处理内存。


1

除了大型但速度较慢的主内存系统之外,现代计算机还具有几层缓存。在从主存储系统读取或写入一个字节所需的时间内,人们可以对最快的高速缓存进行数十次访问。因此,访问一个位置一千次要比访问每个位置1,000个(甚至100个)独立位置快得多。由于大多数应用程序会在堆栈顶部附近重复分配和释放少量内存,因此堆栈顶部的位置会被大量使用和重复使用,因此绝大多数(典型应用程序中超过99%)堆栈访问可以使用高速缓存来处理。

相比之下,如果应用程序要重复创建和放弃堆对象以存储连续信息,则曾经创建的每个堆栈对象的每个版本都必须写到主存储器中。即使在CPU想要回收它们开始的缓存页面时,绝大多数此类对象将完全无用,CPU也无法得知。因此,CPU将不得不浪费大量时间执行对无用信息的慢速存储器写入。不完全是提高速度的秘诀。

要考虑的另一件事是,在许多情况下,知道一旦例程退出就不会使用传递给例程的对象引用是很有用的。如果参数和局部变量是通过堆栈传递的,并且对例程代码的检查表明它没有持久保存传入引用的副本,则调用例程的代码可以确保如果没有外部引用,对象在调用之前存在,之后将不存在。相比之下,如果参数是通过堆对象传递的,则“例程返回之后”之类的概念会变得更加模糊,因为如果代码保留了延续的副本,则例程可能会在返回a之后多次“返回”一次通话。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.