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

5

我尝试在堆内存和栈内存中为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;
}

17
你认为在堆栈上为10^7个整数分配空间的位置在哪里? - Sneftel
3
为什么不应该包含bits/stdc++.h?包含bits/stdc++.h可能会使编译时间变慢,并且这是不可移植的,因为它依赖于特定的实现。此外,它将包含整个标准库,而您可能只需要其中一部分。建议显式地包含所需的头文件。 - Aconcagua
1
关于使用命名空间std的问题 - Aconcagua
2
@ReticulatedSpline 然而,直到您尝试写入该内存时,操作系统才会实际保留该内存。 这是Linux(和其他一些操作系统)的“特性”,如果您重视系统稳定性,可以禁用它。 - Andrew Henle
2
std::vector 也会在堆上分配其数据!只有少数成员变量,包括指针,实际上驻留在堆栈上;尝试使用 sizeof(std::vector<int>),以获取实际分配在堆栈上的字节数... - Aconcagua
显示剩余5条评论
4个回答

17

new int[1e7] 为1e7个int值分配空间,但不初始化它们。

vector<int> v(1e7); 创建一个vector<int>对象在堆栈上,并且该对象的构造函数在堆上为1e7个int值分配空间。同时将每个int值初始化为0。

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

int data[1e7];

但需要注意的是:这可能会失败,因为堆栈不足以容纳这么大的数组。


你做得太棒了! - Tanveer Badar

7
我是一个初学者,但是让我来说出我主要理解的内容以测试自己。

int *p = new int[1e7];

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

vector<int> v(1e7);

您正在为一个vector<int>对象在堆栈上分配内存。该对象的成员之一是指向堆上的一个int [1e7]的指针,也被分配了内存。此外,其中的所有值都使用int()(0)的值进行初始化。详情请参见std::vector的构造函数(2)

6

其他答案指出,向量构造函数中至少存在一个“隐藏”的初始化过程。

但是您的示例还有另一个问题:在C ++中对未经优化的代码进行基准测试几乎毫无意义,并且适当地计时优化后的代码是很困难的。

让我们来看看您(为了可读性而修改)的示例,使用Clang编译并启用-O3优化级别:godbolt链接

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++中对未经优化的代码进行基准测试几乎没有意义”,因为最终你可能会允许编译器优化代码。如果是这个意思,可能有所帮助。但如果你从不打算进行优化,那么基准测试可能就是你想要的。 - villaa

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接