【问题标题】:C++ Array vs vectorC++ 数组与向量
【发布时间】:2009-12-22 11:21:30
【问题描述】:

当使用 C++ 向量时,花费的时间是 718 毫秒, 而当我使用 Array 时,时间几乎是 0 毫秒。

为什么会有这么大的性能差异?

int _tmain(int argc, _TCHAR* argv[])
{
const int size = 10000; 
clock_t start, end; 
start = clock();
vector<int> v(size*size); 
for(int i = 0; i < size; i++)
{  
    for(int j = 0; j < size; j++)
    {   
        v[i*size+j] = 1;  
    } 
} 
end = clock();
cout<< (end - start)
    <<" milliseconds."<<endl; // 718 milliseconds

int f = 0;
start = clock(); 
int arr[size*size]; 
for(int i = 0; i < size; i++)
{  
    for(int j = 0; j < size; j++)
    {   
        arr[i*size+j] = 1;  
    } 
} 
end = clock();
cout<< ( end - start)
    <<" milliseconds."<<endl; // 0 milliseconds
return 0;
}

【问题讨论】:

  • 这是如何编译的?是否启用了优化?你使用的是哪个编译器?
  • int arr[size*size]:导致我的机器出现分段错误(在没有优化的情况下构建);因为把它放在堆栈上似乎超过了编译器允许的堆栈帧的大小:(gcc 版本 4.2.1(Apple Inc. build 5646))stackoverflow.com/questions/216259/…
  • 有趣的是,如果你动态分配数组,我的机器上的时间也会上升到大约 500 毫秒(你应该这样做,因为它太大了。正如 Martin York 所说,你要求否则堆栈溢出)
  • vector 专门用于数组的随机访问,因此我认为它的性能可能会有所折衷,最好在随机访问的情况下使用(插入和删除数组的随机元素)寻求优化。
  • 请注意,唯一正确的答案是下面 el.pescado 给出的答案。如果数组没有被优化掉,那么你也会像 Loki Astari 评论的那样得到堆栈溢出。

标签: c++ visual-studio arrays vector


【解决方案1】:

您的数组 arr 是在堆栈上分配的,即编译器在编译时已计算出必要的空间。在方法的开头,编译器会插入一个汇编语句,如

sub esp, 10000*10000*sizeof(int)

这意味着堆栈指针 (esp) 减少了 10000 * 10000 * sizeof(int) 字节,以便为 100002 个整数的数组腾出空间。这个操作几乎是即时的。

向量是堆分配的,堆分配的成本要高得多。当向量分配所需的内存时,它必须向操作系统请求一块连续的内存,而操作系统必须执行大量工作才能找到这块内存。

正如 Andreas 在 cmets 中所说,你所有的时间都花在了这一行:

vector<int> v(size*size); 

访问循环内的向量与访问数组一样快。

有关其他概述,请参见例如

编辑:

在所有关于性能优化和编译器设置的 cmets 之后,我今天早上做了一些测量。我必须设置size=3000,所以我用大约十分之一的原始条目进行了测量。所有测量均在 2.66 GHz Xeon 上进行:

  1. 使用 Visual Studio 2008 中的调试设置(无优化、运行时检查和调试运行时),向量测试耗时 920 毫秒,而阵列测试耗时 0 毫秒。

    98,48% 的总时间花在了vector::operator[],也就是说,时间确实花在了运行时检查上。

  2. 在完全优化的情况下,向量测试需要 56 毫秒(原始条目数的十分之一),而数组则需要 0 毫秒。

    向量 ctor 需要 应用程序运行时间的 61,72%。

所以我猜每个人都是正确的,具体取决于所使用的编译器设置。 OP 的时序建议优化构建或没有运行时检查的 STL。

一如既往,士气是:首先配置文件,然后优化。

【讨论】:

  • +1 是的,将vector&lt;int&gt; v(size*size); 移出时间,应该没有任何区别。
  • 当然,您可能还需要允许编译器内联内容以获得相同的速度,即不要将速度与优化关闭进行比较
  • 或者你可以将数组设为: int* arr = new int[size*size];这将使用内存分配。但是,除非这些设置成本与您要测量的内容相关,否则不要在您的时间安排中包含这些设置成本。
  • @Daemin 这就是我的想法。这真的是唯一公平的比较。一旦分配了内存,它来自堆栈还是堆都无关紧要,但是是的:进行系统调用来分配内存将会很昂贵。
  • 栈和堆分配的区别应该不能占718毫秒的时间。
【解决方案2】:

如果您使用 Microsoft 编译器进行编译,为了公平比较,您需要通过定义 _SECURE_SCL=0 和 _HAS_ITERATOR_DEBUGGING=0 来关闭迭代器安全检查和迭代器调试。

其次,您使用的构造函数将每个向量值初始化为零,并且在填充数组之前不会将数组设置为零。所以你要遍历向量两次。

试试:

vector<int> v; 
v.reserve(size*size);

【讨论】:

  • vector::reserve 之后你必须调用vector::push_back 来增加向量的大小。使用未经检查的operator[] 会起作用,但它会是邪恶的。 vector::resize 也会初始化为 0。
【解决方案3】:

为了公平比较,我认为以下内容应该是合适的:

#include <sys/time.h>
#include <vector>
#include <iostream>
#include <algorithm>
#include <numeric>


int main()
{
  static size_t const size = 7e6;

  timeval start, end;
  int sum;

  gettimeofday(&start, 0);
  {
    std::vector<int> v(size, 1);
    sum = std::accumulate(v.begin(), v.end(), 0);
  }
  gettimeofday(&end, 0);

  std::cout << "= vector =" << std::endl
        << "(" << end.tv_sec - start.tv_sec
        << " s, " << end.tv_usec - start.tv_usec
        << " us)" << std::endl
        << "sum = " << sum << std::endl << std::endl;

  gettimeofday(&start, 0);
  int * const arr =  new int[size];
  std::fill(arr, arr + size, 1);
  sum = std::accumulate(arr, arr + size, 0);
  delete [] arr;
  gettimeofday(&end, 0);

  std::cout << "= Simple array =" << std::endl
        << "(" << end.tv_sec - start.tv_sec
        << " s, " << end.tv_usec - start.tv_usec
        << " us)" << std::endl
        << "sum = " << sum << std::endl << std::endl;
}

在这两种情况下,都会执行动态分配和解除分配,以及对元素的访问。

在我的 Linux 机器上:

$ g++ -O2 foo.cpp 
$ ./a.out 
= vector =
(0 s, 21085 us)
sum = 7000000

= Simple array =
(0 s, 21148 us)
sum = 7000000

std::vector&lt;&gt; 和数组案例的性能相当。关键是,如果您的代码结构合理,std::vector&lt;&gt; 可以与简单数组一样快。


在相关说明中,关闭优化在这种情况下会产生巨大的影响:

$ g++ foo.cpp 
$ ./a.out 
= vector =
(0 s, 120357 us)
sum = 7000000

= Simple array =
(0 s, 60569 us)
sum = 7000000

Neil 和 jalf 等人的许多优化断言是完全正确的。

HTH!

编辑:更正了强制矢量破坏包含在时间测量中的代码。

【讨论】:

  • 向量解除分配只在此处测量向量测试结束时间后进行,在块的末尾,不是吗?这使得比较代码有点不公平。
  • @Olli:好点子!我已经相应地更新了代码和结果。在进行了更正后,std::vector&lt;&gt; 的情况不再始终更快,但仍与数组的情况相当——有时稍快,有时稍慢。感谢您指出代码中的问题!
【解决方案4】:

将分配更改为例如。 arr[i*size+j] = i*j,或其他一些非常量表达式。我认为编译器优化了整个循环,因为从未使用过分配的值,或者用一些预先计算的值替换数组,所以甚至没有执行循环并且你得到 0 毫秒。

将 1 更改为 i*ji 获得相同的向量和数组计时,除非将 -O1 标志传递给 gcc,然后在这两种情况下我都得到 0 毫秒。

因此,首先,请仔细检查您的循环是否实际执行。

【讨论】:

  • 天啊!这是正确的答案,所有其他答案都遥遥无期。有人认为 400MB 数组的堆栈或堆分配可能很重要吗?甚至在某些实现中可以在堆栈上分配 400MB?很遗憾,这个投票不够。
【解决方案5】:

您可能正在使用 VC++,在这种情况下,默认情况下标准库组件会在运行时执行许多检查(例如索引是否在范围内)。可以通过将一些宏定义为 0 来关闭这些检查(我认为是 _SECURE_SCL)。

另一件事是我什至无法按原样运行您的代码:自动数组对于堆栈来说太大了。当我将其设为全局时,使用 MingW 3.5 时,向量的时间为 627 毫秒,数组的时间为 26875 毫秒(!!),这表明这种大小的数组确实存在很大问题。

对于这个特定的操作(填充值 1),您可以使用向量的构造函数:

std::vector<int> v(size * size, 1);

以及数组的填充算法:

std::fill(arr, arr + size * size, 1);

【讨论】:

    【解决方案6】:

    两件事。一, operator[] 对于向量来说要慢得多。第二,当您一次添加一个元素时,大多数实现中的向量有时会表现得很奇怪。我的意思不仅仅是它分配了更多的内存,而是它有时会做一些真正奇怪的事情。

    第一个是主要问题。对于仅仅一百万字节,即使重新分配内存十几次也不应该花费很长时间(它不会在每个添加的元素上都这样做)。

    在我的实验中,预分配并没有太大改变它的缓慢性。当内容是实际对象时,如果你尝试做一些简单的事情,比如对它进行排序,它基本上会停止。

    结论,不要将 stl 或 mfc 向量用于任何大型或计算量大的东西。它们的实现很差/很慢,会导致大量内存碎片。

    【讨论】:

      【解决方案7】:

      当您声明数组时,它位于堆栈(或静态内存区域)中,它非常快,但无法增加其大小。

      当你声明向量时,它会分配动态内存,它不是那么快,但在内存分配上更灵活,所以你可以改变大小而不是将它的维度设置为最大大小。

      【讨论】:

        【解决方案8】:

        在分析代码时,请确保您正在比较相似的内容。

        vector<int> v(size*size); 
        

        初始化向量中的每个元素,

        int arr[size*size]; 
        

        没有。试试

        int arr[size * size];
        memset( arr, 0, size * size );
        

        然后再次测量...

        【讨论】:

        • 我不同意 - 这是vector 的一个缺陷,即使使用 POD 类型,在您之后要立即手动设置每个元素的情况下也无法避免初始化。在不需要零初始化的情况下,向量与数组的基准测试应该表明数组更快,这是绝对正确的。也就是说,在这种情况下,他手动将所有值初始化为 1,因此将数组代码原样与vector&lt;int&gt; v(size*size,1); 进行比较可能更公平
        • 你试过vector&lt;int&gt; v(0); v.resize( DESIRED_SIZE );吗?它应该导致分配一个空的、大小为零的向量,然后将其大小重新调整为 DESIRED_SIZE,而无需任何构造函数/初始化。
        • 不,resize 真的是void resize(size_type sz, T c = T())。与构造函数相同,它初始化所有新值。
        • 您对此绝对肯定吗? resize() 更改 capacity(),而不是 size()...?!?
        猜你喜欢
        • 2010-12-28
        • 1970-01-01
        • 2019-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-27
        • 2021-01-13
        相关资源
        最近更新 更多