为啥分配堆内存比分配堆栈内存快得多?

Posted

技术标签:

【中文标题】为啥分配堆内存比分配堆栈内存快得多?【英文标题】:Why is allocating heap-memory much faster than allocating stack-memory?为什么分配堆内存比分配堆栈内存快得多? 【发布时间】:2019-12-07 23:26:21 【问题描述】:

我尝试在堆和堆栈内存中为 10^7 个整数分配空间,以查看哪个更快。显然在堆内存中分配要快得多,但我不明白原因。

#include <bits/stdc++.h>
#include <chrono>

using namespace std;
using namespace std::chrono;

int main()

  high_resolution_clock::time_point t1 = high_resolution_clock::now();

  int *p = new int[1e7];

  high_resolution_clock::time_point t2 = high_resolution_clock::now();
  auto duration = duration_cast<microseconds>( t2 - t1 ).count();
  cout << duration / 1e6 << "\n"; // 5e-06



  t1 = high_resolution_clock::now();

  vector<int> v(1e7);

  t2 = high_resolution_clock::now();
  duration = duration_cast<microseconds>( t2 - t1 ).count();
  cout << duration / 1e6 << "\n"; // 0.112284

  return 0;

【问题讨论】:

你认为你在堆栈上为 10^7 个整数分配了空间吗? Why one shouldn't include bits/stdc++.h... About using namespace std... @ReticulatedSpline 但是,在您尝试写入之前,操作系统实际上不会保留该内存。 这是 Linux(以及其他一些操作系统)的“功能” ) 如果您重视系统稳定性,可以禁用它。 std::vector 也在堆上分配它的数据!只有少数成员变量,包括指针,实际上驻留在堆栈上;试试sizeof(std::vector&lt;int&gt;),给出实际分配在堆栈上的字节数... 【参考方案1】:

new int[1e7] 为 1e7 int 值分配空间并且不初始化它们。

vector&lt;int&gt; v(1e7); 在堆栈上创建一个vector&lt;int&gt; 对象,该对象的构造函数在堆上为1e7 个int 值分配空间。它将每个 int 值初始化为 0。

速度的差异是因为初始化。

要比较堆栈分配的速度,您需要在堆栈上分配一个数组:

int data[1e7];

但请注意:这很有可能会失败,因为堆栈不足以容纳那么大的数组。

【讨论】:

你成功了!【参考方案2】:

我只是一个初学者,但让我给出我所了解的主要是为了测试自己。

int *p = new int[1e7];

您正在为堆上的 1000 万个整数分配连续内存。

vector<int> v(1e7);

您正在为vector&lt;int&gt; 对象分配堆栈内存。在该对象的成员中,有一个指向堆上int[1e7] 的指针,该指针也已分配。而且,其中的所有值都用int()的值初始化(带0)。见constructor (2) of std::vector

【讨论】:

【参考方案3】:

其他答案指出向量构造函数中至少有一个“隐藏”初始化。

但是您的示例还有另一个问题:也许它甚至无法衡量您认为的效果。在 C++ 中对未优化的代码进行基准测试几乎没有意义,并且很难对代码进行适当的时序优化。

让我们看一下 Clang 使用 -O3 优化级别编译的(为便于阅读而修改)示例:godbolt link。

double test1() 
  high_resolution_clock::time_point t1 = high_resolution_clock::now();

  int *p = new int[1e7];

  high_resolution_clock::time_point t2 = high_resolution_clock::now();
  auto duration = duration_cast<microseconds>( t2 - t1 ).count();
  return duration / 1e6; // 5e-06

编译为:

test1():                              # @test1()
        push    rbx
        call    std::chrono::_V2::system_clock::now()
        mov     rbx, rax
        call    std::chrono::_V2::system_clock::now()
        sub     rax, rbx
        movabs  rcx, 2361183241434822607
        imul    rcx
        mov     rax, rdx
        shr     rax, 63
        sar     rdx, 7
        add     rdx, rax
        cvtsi2sd        xmm0, rdx
        divsd   xmm0, qword ptr [rip + .LCPI0_0]
        pop     rbx
        ret
.LCPI1_0:
        .quad   4696837146684686336     # double 1.0E+6

第一部分甚至没有调用 operator new!编译器查看了您的程序并意识到您从未使用分配的数组,因此它从生成的可执行文件中删除了分配。

因此,当使用这样的设置进行编译时,程序的第一部分根本不会在堆上分配数组,从而使测量变得毫无意义。

我建议阅读有关基准测试的内容并使用专门的微基准测试框架来进行此类测试。查看Google Benchmark(和在线QuickBench)及其文档。

【讨论】:

我喜欢你的回答,这很重要——我认为你说“在 C++ 中对未优化代码进行基准测试毫无意义”,因为最终你会可能允许编译器优化代码。如果这就是意思,补充一下可能会有所帮助。在我看来,如果您从不打算优化它,那么基准测试可能就是您想要的。【参考方案4】:

我想指出,堆栈分配在运行时绝对不需要时间;所有工作都由编译器完成。 无论优化如何,比较都是没有意义的。

【讨论】:

以上是关于为啥分配堆内存比分配堆栈内存快得多?的主要内容,如果未能解决你的问题,请参考以下文章

Java中堆栈的区别

为啥要在堆上而不是栈上分配内存? [复制]

Java 中的堆和栈

C++ 优化 - 堆栈分配的数组类型与外部链接维度?

Java中堆内存与栈内存分配浅析

Java内存的堆栈与常量池