这个问题可能听起来相当简单,但这是我与另一位开发人员的争论。

我小心地在我可以的地方进行堆栈分配,而不是堆分配。他和我说话,看着我的肩膀,并评论说,这是没有必要的,因为他们的表现是一样的。

在我的印象中,增加堆栈是一个常数时间,而堆分配的性能取决于当前堆的复杂性,包括分配(找到合适大小的孔)和反分配(缩小孔以减少碎片,如果我没有弄错的话,许多标准库实现在删除过程中需要时间来做这件事)。

在我看来,这可能非常依赖于编译器。在这个项目中,我特别使用了一个用于PPC架构的Metrowerks编译器。对这种组合的深入了解将是最有帮助的,但一般来说,对于GCC和msvc++,情况如何?堆分配不如堆栈分配高效吗?没有区别吗?还是差异如此之小以至于变成了毫无意义的微观优化。


当前回答

正如其他人所说,堆栈分配通常要快得多。

但是,如果复制对象的代价很高,那么如果不小心,在堆栈上分配可能会导致以后使用对象时的巨大性能损失。

例如,如果你在堆栈上分配了一些东西,然后将其放入容器中,那么在堆上分配并将指针存储在容器中会更好(例如使用std::shared_ptr<>)。同样的情况也适用于按值传递或返回对象,以及其他类似的情况。

重点是,尽管在许多情况下堆栈分配通常比堆分配更好,但有时如果你在不最适合计算模型的情况下费尽脑汁进行堆栈分配,它可能会导致比它解决的问题更多的问题。

其他回答

我想说的是,实际上GCC生成的代码(我还记得VS)不需要做堆栈分配的开销。

对以下函数表示:

  int f(int i)
  {
      if (i > 0)
      {   
          int array[1000];
      }   
  }

下面是生成的代码:

  __Z1fi:
  Leh_func_begin1:
      pushq   %rbp
  Ltmp0:
      movq    %rsp, %rbp
  Ltmp1:
      subq    $**3880**, %rsp <--- here we have the array allocated, even the if doesn't excited.
  Ltmp2:
      movl    %edi, -4(%rbp)
      movl    -8(%rbp), %eax
      addq    $3880, %rsp
      popq    %rbp
      ret 
  Leh_func_end1:

所以无论你有多少局部变量(甚至在if或switch内部),只有3880会改变为另一个值。除非你没有局部变量,否则这条指令只需要执行。所以分配局部变量没有开销。

堆栈分配是一对指令,而我所知道的最快的rtos堆分配器(TLSF)平均使用150条指令。此外,堆栈分配不需要锁,因为它们使用线程本地存储,这是另一个巨大的性能优势。因此,堆栈分配可以快2-3个数量级,这取决于您的多线程环境有多严重。

通常,如果关心性能,堆分配是最后的选择。一个可行的中间选项可以是一个固定池分配器,它也只有几个指令,每次分配开销很小,所以它非常适合固定大小的小对象。缺点是它只适用于固定大小的对象,本质上不是线程安全的,并且有块碎片问题。

一般来说,正如上面几乎每个答案所提到的,堆栈分配比堆分配快。堆栈的push或pop是O(1),而从堆中分配或释放可能需要遍历之前的分配。但是,您通常不应该在紧凑的性能密集型循环中进行分配,因此选择通常取决于其他因素。

做出这样的区分可能会有好处:您可以在堆上使用“堆栈分配器”。严格地说,我认为堆栈分配是指分配的实际方法,而不是分配的位置。如果你在实际的程序堆栈上分配了很多东西,这可能会因为各种各样的原因而变得很糟糕。另一方面,在可能的情况下使用堆栈方法在堆上进行分配是分配方法的最佳选择。

既然你提到了《Metrowerks》和《PPC》,我猜你指的是Wii。在这种情况下,内存是非常宝贵的,在任何可能的情况下使用堆栈分配方法都可以保证您不会在片段上浪费内存。当然,这样做需要比“普通”堆分配方法更加小心。对每种情况进行权衡是明智的。

堆栈分配要快得多,因为它所做的只是移动堆栈指针。 使用内存池,您可以从堆分配中获得类似的性能,但这会略微增加复杂性,并带来令人头痛的问题。

此外,堆栈与堆不仅是性能方面的考虑;它还告诉您许多关于对象的预期生存期的信息。

通常,堆栈分配只是由堆栈指针寄存器中的减法组成。这比搜索堆快多了。

Sometimes stack allocation requires adding a page(s) of virtual memory. Adding a new page of zeroed memory doesn't require reading a page from disk, so usually this is still going to be tons faster than searching a heap (especially if part of the heap was paged out too). In a rare situation, and you could construct such an example, enough space just happens to be available in part of the heap which is already in RAM, but allocating a new page for the stack has to wait for some other page to get written out to disk. In that rare situation, the heap is faster.