为什么在我的机器上,std::vector的分配和释放比动态数组慢?

4
我曾认为std::vector只是动态数组的一个简单封装,因此它们的性能应该相当。互联网和stackoverflow本身也给出了同样的答案。然而,当我自己测试时,发现有很大的差异。以下是代码。我尝试了VC++ 2012(发布版本)和带有优化标志-O2的MinGW。
new、malloc和calloc的时间约为0.005秒,而std::vector在两个编译器上都需要0.9秒。std::vector本质上很慢吗?还是我的代码有严重缺陷?
#define _SCL_SECURE 0
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <time.h>

struct timer
{
    clock_t start;
    timer()
    {
        start=clock();
    }
    ~timer()
    {
        double t=static_cast<double>(clock()-start)/CLOCKS_PER_SEC;
        printf("%lf\n", t);
    }
};

int main()
{
    const size_t len=(1<<28);   
    {
        timer t;
        int *d=new int[len];
        printf("%d\n",d[len-1]);//prevent compiler from optimizing away 
                                //useless allocation and deallocation
        delete[] d;
    }
    {
        timer t;
        int *d=(int *)malloc(len*sizeof(int));
        printf("%d\n", d[len-1]);
        free(d);
    }

    {
        timer t;
        std::vector<int> d(len);
        printf("%d\n", d[len-1]);
    }
    {
        timer t;
        int *d=(int *)calloc(len, sizeof(int));
        printf("%d\n", d[len-1]);
        free(d);
    }

    return 0;
}

编辑 1

按建议,我尝试了创建动态数组的其他方式。

  • new:为0.005
  • malloc:为0.005
  • calloc:为0.005
  • malloc+memset:为1.244
  • vector(len):为1.231
  • vector(len, 0):为1.234
  • vector.reserve(len):为0.005

因此,罪魁祸首确实是零初始化而不是分配或释放内存。这意味着,如果需要一个零初始化的数组,即使vector具有默认初始化所有元素的构造函数,也不能使用它。

此外,这不仅仅是我的想法。我的一门课程的最终项目是根据所花费的时间评分的,我使用了几个 vector 来分配一个巨大的内存缓冲区,而不是使用new来保证异常安全性,并且我们的教科书鼓励使用STL。直到今天我才意识到由于这个原因我失去了一些分数。很遗憾。

编辑 2

今天我在Ubuntu 13.04 x64上尝试了Clang 3.2,而std :: vector现在不再需要花费那么长时间进行初始化。事实上,vector现在是最快的!也许这真的是编译器优化问题,而不是std :: vector设计本身的问题。


2
你不应该运行测试数千次以上(如果可能的话)吗? - Son-Huy Pham
1
使用GCC 4.8.1和 -O3,情况甚至更糟。除了向量外,其他所有内容的时间都是0.000000(第二次运行链接时,向量时间相对较低)。 - chris
2
你应该比较执行相同功能的代码(例如,你是否阅读了向量文档?)。你应该比较没有未定义行为的代码。 - R. Martinho Fernandes
4
vector 和其他代码的重要区别在于,vector 在初始化内存时会将其清零,而其他代码则不会(除了 calloc)。如果您将 new 替换为 new int[len](),它会与 vector 执行相同的时间。我认为 calloc 在某种程度上进行了优化(例如,它可能会请求清零的内存,而不是显式地清零内存本身)。 - Mankarse
你认为 #define _SCL_SECURE 0 是什么意思? - James McNellis
显示剩余2条评论
2个回答

4

我认为这是由于std::vector的分配会对每个元素调用复制构造函数,而malloc仅返回未初始化的内存。

std::vector<int> x(100); 

实际上等同于:

std::vector<int> y(100, int()); 

请参阅std :: vector构造函数的文档:http://en.cppreference.com/w/cpp/container/vector/vector 我经常会像这样做:
std::vector<int> x; 
x.reserve(100);
// Insert items into x via x.push_back()

7
在C++11(和Visual C++ 2012)中,“std::vector<T> x(100);”与“std::vector<T> y(100, T())”不同。前者默认构造100个元素,而后者从给定的“T()”参数复制构造100个元素。(对于“int”类型,效果相同,但对于所有类型都是如此的情况并非如此。) - James McNellis
对于原始类型,newmalloccalloc 是相同的吗? - Siyuan Ren
@JamesMcNellis:现在我对 C++ 的怪癖有了更多的理解,我发现你措辞上有一个错误。在 C++11 中,std::vectorvalue 初始化其内容,而不是 default 初始化。 - Siyuan Ren

3
printf("%d\n",d[len-1]);//prevent compiler from optimizing away 

这行代码从一个未初始化的对象中读取数据。与其阻止编译器进行优化,它给了编译器自由发挥的空间(即程序的行为是未定义的)。

假设我们已经修复了这个问题,现在程序的行为是明确定义的(可能我们添加了一行代码来初始化d[len-1])。

std::vector<int> d(len);

这一行初始化了len个值为0的对象。而另一行则没有:

int *d=new int[len];

唯一另一个导致len对象值为0的行是这个:

int *d=(int *)calloc(len, sizeof(int));

唯一可以从与分配和释放性能相关的基准测试中得出的结论是,该基准测试不适合得出与分配和释放性能相关的结论。

我知道它从未初始化的内存中读取。但实际上我并不需要这个值(可能是剩余的任何字节)。除了打印出随机值而不是零,它可能是什么“未定义”行为? - Siyuan Ren
4
“谁知道呢”,你认为“未定义”是什么意思?它并不意味着“被定义为剩余的任何字节”。它的意思是“未定义”。 - R. Martinho Fernandes
3
据我所知,从未初始化的内存中读取数据并不是未定义行为。如果我有误请纠正我,但我一直认为从未初始化的内存块中读取数据被定义为返回任何值(例如一个随机的二进制序列)。 - yyny
在这个例子中,它不是未定义行为,因为这个变量不能被优化为“寄存器”:https://dev59.com/v2ct5IYBdhLWcg3wmOng - Victor Sergienko
@Victor,这是UB,因为标准规定它是UB。变量可以被“优化为register”,因为它是UB。这就是UB的含义:任何事情都可能发生,包括变量“优化为register”。 - R. Martinho Fernandes
显示剩余2条评论

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