老实说,编写一个程序来比较性能很简单:
#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 ticks\n";
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 ticks\n";
}
有人说愚蠢的一致性是小头脑的妖精。显然,优化编译器是许多程序员的专精。该讨论曾经是答案的底部,但是显然人们不愿意阅读那么远的内容,因此,我将其移至此处以避免收到已经回答的问题。
优化的编译器可能会注意到此代码不执行任何操作,并且可能会对其进行优化。做这样的事情是优化器的工作,而与优化器抗争是傻瓜的事。
我建议在关闭优化的情况下编译此代码,因为没有很好的方法来欺骗当前正在使用或将来将要使用的每个优化器。
任何打开优化器然后抱怨与优化器抗争的人都应该受到公众的嘲笑。
如果我关心纳秒精度,我不会使用std::clock()
。如果我想将结果发表为博士学位论文,那么我会做更多的事情,并且我可能会比较GCC,Tendra / Ten15,LLVM,Watcom,Borland,Visual C ++,Digital Mars,ICC和其他编译器。实际上,堆分配比栈分配要花费数百倍的时间,而且我认为进一步研究这个问题没有任何用处。
优化器的任务是摆脱我正在测试的代码。我看不出有任何理由告诉优化器运行,然后尝试使优化器欺骗而不进行实际优化。但是,如果我认为这样做有价值,那么我将执行以下一项或多项操作:
向中添加数据成员empty
,并在循环中访问该数据成员;但是,如果我只从数据成员中读取数据,则优化器可以进行不断折叠并删除循环;如果我只写数据成员,则优化器可能会跳过循环的最后一个迭代,而不是最后一个迭代。另外,问题不是“堆栈分配和数据访问与堆分配和数据访问”。
声明e
volatile
,但volatile
通常编译不正确(PDF)。
获取e
循环内部的地址(并可能将其分配给extern
在另一个文件中声明和定义的变量)。但是即使在这种情况下,编译器可能会注意到-至少在堆栈上- e
总是会分配在相同的内存地址,然后像上面的(1)一样进行常量折叠。我得到了循环的所有迭代,但是从未真正分配对象。
除了显而易见的以外,该测试还存在缺陷,因为它可以同时测量分配和释放,并且最初的问题并未询问释放。当然,分配在堆栈上的变量会在其作用域的末尾自动释放,因此不要调用delete
(1)倾斜数字(关于堆栈分配的数字中包括堆栈释放,因此仅测量堆释放是合理的)和( 2)导致非常严重的内存泄漏,除非我们保留对新指针的引用并delete
在进行时间测量后调用。
在我的机器上,在Windows上使用g ++ 3.4.4,对于小于100000分配的任何内容,对于堆栈和堆分配,我都会获得“ 0时钟滴答”,即使这样,对于堆栈分配和“ 15时钟滴答”也将获得“ 0时钟滴答”。 ”进行堆分配。当我测量10,000,000个分配时,堆栈分配需要31个时钟滴答,堆分配需要1562个时钟滴答。
是的,优化编译器可能会忽略创建空对象。如果我理解正确,它甚至可能会漏掉整个第一个循环。当我将迭代次数提高到10,000,000时,堆栈分配花费了31个时钟周期,堆分配花费了1562个时钟周期。我认为可以肯定地说,在没有告诉g ++优化可执行文件的情况下,g ++并没有忽略构造函数。
自从我写这篇文章以来,多年来,Stack Overflow一直偏向于发布经过优化的版本的性能。总的来说,我认为这是正确的。但是,我仍然认为在您实际上不希望优化代码时要求编译器优化代码是很愚蠢的。我觉得这与为代客停车支付额外费用非常相似,但拒绝交出钥匙。在这种情况下,我不希望优化程序运行。
使用基准的略微修改版本(以解决原始程序每次都没有通过循环在栈上分配某些东西的有效点),并在不进行优化的情况下进行编译,但链接到发行版库(以解决我们不希望使用的有效点)不想包含任何由于链接到调试库而导致的速度下降):
#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 seconds\n", 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 seconds\n", 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
。
您可能不同意我获取未优化构建的方法。很好:随时随地修改基准。启用优化后,我得到:
on_stack took 0.000000 seconds
on_heap took 51.608723 seconds
不是因为堆栈分配实际上是瞬时的,而是因为任何半个体面的编译器都可以注意到它on_stack
并没有做任何有用的事情,并且可以进行优化。我的Linux笔记本电脑上的GCC还注意到它on_heap
并没有做任何有用的事情,并对其进行了优化:
on_stack took 0.000003 seconds
on_heap took 0.000002 seconds