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

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

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

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


当前回答

前面提到过,堆栈分配只是移动堆栈指针,即大多数架构上的一条指令。将其与堆分配中通常发生的情况进行比较。

操作系统以链表的形式维护部分空闲内存,有效负载数据由指向空闲部分起始地址的指针和空闲部分的大小组成。为了分配X字节的内存,将遍历链表,并按顺序访问每个音符,检查其大小是否至少为X。当找到大小为P >= X的部分时,将P分成大小为X和P-X的两个部分。更新链表并返回指向第一部分的指针。

如您所见,堆分配取决于许多因素,如您请求的内存大小、内存的碎片程度等等。

其他回答

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

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

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

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

我认为生命期很重要,被分配的东西是否必须以复杂的方式构造。例如,在事务驱动的建模中,您通常必须填写并将带有一堆字段的事务结构传递给操作函数。以OSCI SystemC TLM-2.0标准为例。

在栈上靠近操作调用的地方分配这些资源往往会导致巨大的开销,因为这种构造非常昂贵。好的方法是在堆上分配和重用事务对象,或者通过池或简单的策略,如“这个模块只需要一个事务对象”。

这比在每个操作调用上分配对象快很多倍。

原因很简单,该对象具有昂贵的结构和相当长的使用寿命。

我会说:两种都试试,看看哪种最适合你,因为这真的取决于你代码的行为。

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

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位的程序来说,这(目前)不是问题,但是对于具有大量线程的长时间运行的程序来说,它肯定会造成严重破坏。由于碎片化而导致虚拟地址空间耗尽总是一件令人痛苦的事情。

自然,堆栈分配更快。使用堆分配,分配器必须在某处找到空闲内存。使用堆栈分配,编译器只需要给你的函数一个更大的堆栈框架就可以完成,这意味着分配完全不需要花费时间。(我假设您没有使用alloca或任何东西来动态分配堆栈空间,但即使这样,它也非常快。)

但是,您必须警惕隐藏的动态分配。例如:

void some_func()
{
    std::vector<int> my_vector(0x1000);
    // Do stuff with the vector...
}

您可能认为这会在堆栈上分配4 KiB,但您错了。它在堆栈上分配vector实例,但该vector实例又在堆上分配它的4 KiB,因为vector总是在堆上分配它的内部数组(至少除非您指定了一个自定义分配器,这里我不会深入讨论)。如果您希望使用类似stl的容器在堆栈上进行分配,则可能需要std::array或boost::static_vector(由外部boost库提供)。

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

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

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