c - c 堆栈分配还是heap分配速度快

  显示原文与译文双语对照的内容

这个问题听起来很简单,但这是我与另一个开发者的争论。

我在考虑堆栈分配,而不是堆分配它们。 他与我和守护在我的肩膀和用户认为它的已经没有必要展开谈判,因为它们是相同的具备优越的性能。

我一直认为,增长的堆栈是固定的时间,分配性能取决于分配( 寻找合适大小的孔) 和 de-allocating ( 折叠孔,减少碎片,尽可能多的标准库的实现过程中需要一些时间执行这里操作将删除如果我没弄错的话)的堆的当前复杂性。

这让我觉得很可能是编译器依赖的东西。 对于这个项目,特别是我使用了一个 Metrowerks 编译器,用于 PPC架构。 对这个组合的洞察力最有帮助,但是总的来说,对于GCC和MSVC++来说,情况是什么? 堆栈分配不如堆栈分配执行的高? 没有区别或者差异如此之大,以至于它变成无意义的micro-optimization 。

时间:

堆栈分配速度快得多,因为它真正做的就是移动堆栈指针。 使用内存池,你可以从堆分配中获得可比的性能,但这会带来略微增加的复杂性和它自己的头痛。

此外,堆栈 vs 堆不仅仅是性能考虑;它还告诉你对象的预期生命周期。

堆栈快得多。它只在大多数架构上使用单个指令,大多数情况下,在x86上使用 比如:

 
sub esp, 0x10

 

( 将堆栈指针向下移动 0 x10字节,从而使变量使用这些字节。)

当然,堆栈的大小非常有限,因为你会很快发现是否过度使用堆栈分配或者尝试执行无限递归:- )

此外,没有理由优化不可验证需要的代码的性能,如通过分析演示。 "过早优化"通常会导致更多的问题。

在大小,我 stack-allocate it,我的出来的经验,如果我知道你们交出一些数据在 compile-time,而且它是几百bytes.下 否则我将 heap-allocate 。

老实说,编写一个程序来比较性能是很简单的:


#include <ctime>
#include <iostream>

namespace {
 class empty { };//even empty classes take up 1 byte of space, minimum
}

int main()
{
 std::clock_t start = std::clock();
 for (int i = 0; i <100000; ++i)
 empty e;
 std::clock_t duration = std::clock() - start;
 std::cout <<"stack allocation took" <<duration <<" clock ticksn";
 start = std::clock();
 for (int i = 0; i <100000; ++i) {
 empty* e = new empty;
 delete e;
 };
 duration = std::clock() - start;
 std::cout <<"heap allocation took" <<duration <<" clock ticksn";
}

据说一个愚蠢的坚持才是hobgoblin小地思想。 显然优化编译器是许多程序员头脑的hobgoblins 。 这个讨论以前是底部的回答的重新定位,但人们显然不可能是耐心阅读,所以我到这里,以免列标题问题我已经回答的也就是会搬走。

一个优化编译器可能会注意到,这段代码不执行任何操作,并且可以把它们全部优化掉。 工作就交优化器做的那样的视频和战斗优化器是一个傻瓜的差事。

编译该代码我推荐你在使用优化已经关闭,因为没有好的方法来欺骗网络中每一个优化器当前正在使用或者将要在今后使用。

任何人如果顺序优化器在它然后抱怨战斗应该是受公众嘲弄的靶子。

如果我关心纳秒精度,我将不会使用 std::clock() 。 如果我想将结果格式化为篇学位论文,我会成为一个更大的发布了很多关于这一点,和我极可能比较 GCC,tendra/ten15,LLVM,mq算术编码器等,文本文件或者二进制。Visual C++ 。数字火星,计算机工程和其他编译器。 因为它是比堆栈分配,堆分配需要数以百计的倍的时间,我没有看到什么有用的关于调查这个问题的任何进一步。

优化器有一个任务来删除我正在测试的代码。 我看不到任何理由告诉优化器运行事实上,然后尝试,用来迷惑优化器将使无法优化。 但是如果我看到这样做的价值,我将执行以下一项或者多项操作:

  1. 将数据成员添加到 empty,而且仅在循环中访问该数据成员;但如果我以往任何时候都从文件中读入的数据成员在循环优化器可以执行常数合并和移除操作;如果我只想开发到数据成员,则优化器可能会跳过所有的但非常循环的最后一次迭代。 此外,问题不是"堆栈分配和数据访问 vs 堆分配和数据访问"。"

  2. 声明 evolatile,但 volatile 通常编译不正确的 ( PDF ) 。

  3. 采取( 并将它的分配给声明为 extern 并在另一个文件中定义的变量) e的地址内的循环。 但即使在这种情况下,编译器可能会注意到在堆栈上--至少 -- e 总是会被分配在同一内存地址,然后执行常量折叠喜欢在上面( 1 ) 。 我得到循环的所有迭代,但对象实际上从未被分配。

显而易见的,这个测试是的缺点,即它以外的措施这两种分配和取消分配,和原来的问题没有要求一下释放。 我们已经得到了一个时间后measurement,当然变量在堆栈上分配它们的范围的结束处被自动释放,所以不调用 delete 将( 1 ) 扭曲这些数字( 堆栈分配包含在关于堆栈分配的数字中,因此只对堆释放进行度量是公平的) 和( 2 ) 造成十分严重的内存泄漏,除非我们保留一个指向新的指针并调用

在我的机器上,使用 G++ 3.4.4 Windows,我得到" 0时钟刻度"上为这两个栈和堆分配而一同 100000拨款,但即便如此,我得到的堆栈分配和堆分配的" 15时钟刻度"" 0时钟刻度"。 在测量 10,000时,000分配,堆栈分配需要 31时钟刻度,堆分配需要 1562时钟刻度。


是,优化编译器可能会忽略创建空对象。 如果我理解正确的话,它甚至会忽略整个第一个循环。 当我将迭代增加到 10,000,000堆栈分配花费 31时钟刻度,堆分配占用 1562时钟刻度。 我认为不告诉 G++ 优化可执行文件是安全的,G++ 没有省略构造函数。


在适宜builds,在未来几年自从我写了这个,该首选项在栈溢出时被performance.发到 一般来说,我认为这是正确的。 事实上不希望这段代码optimized,然而,我还是认为它是愚蠢的去问编译器优化代码当你。 它打动我,这非常类似于支付额外的有关代客泊车,但是拒绝再移交密钥。 在这种情况下,我不希望优化器运行。

使用的基准测试( 在堆栈上每执行一次loop,解决的有效一点原来的程序没有动态的添加一些东西) 及编制一个稍作修改的版本,不需要优化,但使用链接来释放库( 为了解决所产生的有效点,我们不想包括任何减速来调试库链接) 。


#include <cstdio>
#include <chrono>

namespace {
 void on_stack()
 {
 int i;
 }

 void on_heap()
 {
 int* i = new int;
 delete i;
 }
}

int main()
{
 auto begin = std::chrono::system_clock::now();
 for (int i = 0; i <1000000000; ++i)
 on_stack();
 auto end = std::chrono::system_clock::now();

 std::printf("on_stack took %f secondsn", std::chrono::duration<double>(end - begin).count());

 begin = std::chrono::system_clock::now();
 for (int i = 0; i <1000000000; ++i)
 on_heap();
 end = std::chrono::system_clock::now();

 std::printf("on_heap took %f secondsn", std::chrono::duration<double>(end - begin).count());
 return 0;
}

显示:


on_stack took 2.070003 seconds
on_heap took 57.980081 seconds

在我的系统中使用 命令行 编译时 cl foo.cc/Od/MT/EHsc

你可能不同意我获取non-optimized构建的方法。 这不成问题:你可以随意修改基准要多少有多少。 当我打开优化时,我得到:


on_stack took 0.000000 seconds
on_heap took 51.608723 seconds

并不是因为堆栈分配实际上是瞬时的,而是因为任何half-decent编译器都可以注意到 on_stack 没有做任何有用的事情,并且可以优化。 GCC在我的Linux笔记本电脑同时 on_heap 通知,也不做任何有用的工作,并优化它扔掉不错:


on_stack took 0.000003 seconds
on_heap took 0.000002 seconds

一件有趣的事情我了解到堆栈上 vs 堆分配在 Xbox 360氙气处理器,这可能也适用于其他多核心系统,都是分配在堆上造成一个临界区必须要阻止所有其他输入的芯,以使alloc不冲突。 因此,在紧密循环中,堆栈分配是固定大小数组的一种方式,因为它阻止了停滞。

如果你正在为多核/multiproc编码,这可能是另一种加速方式,因为你的堆栈分配只能由运行你的作用域函数的内核查看,并且不会影响任何其它内核/CPU 。

你可以为非常高性能的对象编写特殊的堆分配器。 然而,的heap堆分配器并不是特别高性能。

好点 !

除了orders-of-magnitude性能优势优于堆分配之外,对于长期运行的服务器应用程序,堆栈分配更可取。 即使是最好的托管堆最终也会变得如此零碎,以至于应用程序性能下降。

堆栈分配通常只包含从堆栈指针寄存器中减去。 这比搜索堆快得多。

有时堆栈分配需要添加一个虚拟内存页面。 添加一个新的zeroed内存页面不需要从磁盘读取页面,所以通常这比搜索堆( 尤其是当部分堆被调出时) 快得多。 在罕见的情况,并且你可能会构建这样一个示例中,足够的空间就在堆而它已经是内存中的一部分是可以使用的,但是分配堆栈打开新的网页,为你等待一些其他页面来获得写出到磁盘。 在这种情况下,堆更快。

我不认为堆栈分配和堆分配通常是 interchangable 。 我也希望它们的性能对一般的使用都足够了。

我强烈建议使用小项目,不管哪一个更适合于分配范围。 对于大项目,可能需要堆。

在有多个线程的32位 操作系统上,堆栈通常是有限的( 通常至少有几个 mb ),因为地址空间需要被分割,或者迟早一个线程堆栈会被一个线程堆栈。 在单一线程系统( Linux glibc单一线程) 中,限制的限制很小,因为堆栈可以增长并增长。

在 64位 操作系统上,有足够的地址空间使得线程栈非常大。

不是jsut堆栈分配的速度更快。 你在使用堆栈变量方面也赢得了很多。 它们具有更好的引用位置。 最后,释放成本也要便宜得多。

堆栈分配几乎总是比堆分配快或者快,尽管堆分配器可以简单地使用基于堆栈的分配技术。

然而,在处理基于堆栈的vs 堆的总体性能时存在较大的问题。 通常,堆( 外部) 分配是缓慢的,因为它处理许多不同种类的分配和分配模式。 减少你正在使用的分配器的作用域,它将在没有任何主要更改的情况下提高性能。 在分配模式中添加更好的结构,例如在分配和释放对上强制LIFO排序也可以使用更简单更结构化的方式来提高分配器的性能。 或者,你可以使用或者编写为你的特定分配模式调整的分配器;大多数程序会频繁地分配一些离散的大小,所以基于一些固定的( 最好知道) 大小的堆会执行非常好的操作。 Windows 出于这个原因使用它的low-fragmentation-heap 。

而且充满着风险如果你有太多的threads,另一方面,stack-based分配到快 32位 记忆范围也是 fraught. 栈需要一个连续的内存范围,因此你拥有的线程越多,在没有堆栈溢出的情况下,它们所需要的虚拟地址空间就越多。 在长时间运行的程序中通过大量的threads,与 64位 ( 现在) 这样做不会产生问题,但这肯定会大肆 havoc. 由于碎片而耗尽虚拟地址空间总是一个痛苦。

...