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

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

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

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


当前回答

尽管堆分配器可以简单地使用基于堆栈的分配技术,但堆栈分配几乎总是与堆分配一样快或更快。

However, there are larger issues when dealing with the overall performance of stack vs. heap based allocation (or in slightly better terms, local vs. external allocation). Usually, heap (external) allocation is slow because it is dealing with many different kinds of allocations and allocation patterns. Reducing the scope of the allocator you are using (making it local to the algorithm/code) will tend to increase performance without any major changes. Adding better structure to your allocation patterns, for example, forcing a LIFO ordering on allocation and deallocation pairs can also improve your allocator's performance by using the allocator in a simpler and more structured way. Or, you can use or write an allocator tuned for your particular allocation pattern; most programs allocate a few discrete sizes frequently, so a heap that is based on a lookaside buffer of a few fixed (preferably known) sizes will perform extremely well. Windows uses its low-fragmentation-heap for this very reason.

另一方面,如果线程太多,在32位内存范围上基于堆栈的分配也充满了危险。堆栈需要一个连续的内存范围,因此线程越多,就需要更多的虚拟地址空间来让它们在没有堆栈溢出的情况下运行。对于64位的程序来说,这(目前)不是问题,但是对于具有大量线程的长时间运行的程序来说,它肯定会造成严重破坏。由于碎片化而导致虚拟地址空间耗尽总是一件令人痛苦的事情。

其他回答

我不认为堆栈分配和堆分配通常是可以互换的。我也希望它们的性能都足以用于一般用途。

我强烈推荐小件物品,哪种更适合分配范围。对于较大的项,堆可能是必要的。

在有多个线程的32位操作系统上,堆栈通常是相当有限的(尽管通常至少是几mb),因为需要分割地址空间,迟早一个线程堆栈会碰到另一个线程堆栈。在单线程系统(至少是Linux glibc单线程)上,限制要小得多,因为堆栈可以不断增长。

在64位操作系统上,有足够的地址空间使线程堆栈相当大。

我想说的是,实际上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会改变为另一个值。除非你没有局部变量,否则这条指令只需要执行。所以分配局部变量没有开销。

堆栈的容量有限,而堆则不是。一个进程或线程的典型堆栈大约是8K。一旦分配,就不能更改大小。

堆栈变量遵循作用域规则,而堆变量则不遵循。如果你的指令指针超出了一个函数,所有与该函数相关的新变量都会消失。

最重要的是,您无法预先预测整个函数调用链。因此,仅200字节的分配就可能导致堆栈溢出。如果您正在编写一个库,而不是应用程序,这一点尤其重要。

关于Xbox 360 Xenon处理器上的堆栈与堆分配,我了解到一件有趣的事情,这可能也适用于其他多核系统,即在堆上分配会导致进入临界区以停止所有其他核,这样分配就不会发生冲突。因此,在一个紧密循环中,堆栈分配是固定大小数组的方法,因为它可以防止停顿。

如果您正在为多核/多进程编码,这可能是另一个需要考虑的加速,因为您的堆栈分配将只由运行您的作用域函数的核心可见,而不会影响任何其他内核/ cpu。

除了与堆分配相比具有数量级的性能优势外,堆栈分配对于长时间运行的服务器应用程序更可取。即使是管理得最好的堆最终也会碎片化,导致应用程序性能下降。