【问题标题】:Yet another Dynamic Array vs. std::vector, but另一个动态数组与 std::vector,但是
【发布时间】:2010-07-01 02:06:10
【问题描述】:

...好吧,我得到了奇怪的结果!

我很好奇std::vector 与动态数组的性能对比。鉴于已经有很多关于这个主题的问题,如果我没有经常得到这些“矛盾”的结果,我就不会提到它:vector<int>new int[] 快!我一直认为,如果有任何性能差异,它总是倾向于动态数组。这个结果怎么可能?

代码如下:

int numElements = 10000000;
 long j = 0;
 long k = 0;

 vector<int> intVector(numElements);
 int* intArray = new int[numElements]; 

 clock_t start, finish;
 start = clock();

 for (int i = 0; i < numElements; ++i)
  intVector[i] = i;
 for (int i = 0; i < numElements; ++i)
  j += intVector[i];

 finish = clock();
 cout << "j: " << j << endl;
 cout << "Total duration: " << (double) finish - start << " ms." << endl;

 // Test Control.
 start = clock();

 for (int i = 0; i < numElements; ++i)
  intArray[i] = i;
 for (int i = 0; i < numElements; ++i)
  k += intArray[i];

 finish = clock();
 cout << "k: " << k << endl;
 cout << "Total duration: " << (double) finish - start << " ms." << endl;

优化已开启,我在每个开始/结束块中分离了 for 循环,以便我可以分别为数组/向量的初始化计时(在这种情况下,std::vector&lt;int&gt;new int[] 似乎执行相同)。

但是,使用上面的代码,我经常得到std::vector&lt;int&gt; 赢得26-30 ms36-45 ms 赢得new int[]

有人愿意解释为什么向量的性能优于动态数组吗?两者都是在时序循环之前声明的,所以我预计性能大致相同。此外,我尝试了相同的想法,而不是使用 std::vector&lt;int*&gt;new int*[] 并得到了类似的结果,vector 类的性能优于动态数组,因此指向指针的指针也是如此。

感谢您的帮助。

附录:如果没有优化,std::vector 会在动态数组(~1,400 ms vs. ~80 ms)上浪费大量时间,以提供预期的性能差异,但这并不意味着向量类可以以某种方式进行优化以提供比标准动态数组更好的性能?

【问题讨论】:

  • 尝试循环更长的时间,至少几秒钟。如果您像这样使用向量,则应该几乎没有区别,除非您的编译器进行了一些奇怪的优化。也可以尝试禁用优化。
  • 好吧,用 100M(而不是 10M)尝试它会得到 ~270ms 和 ~360ms,和以前一样,所以结果没有区别。但是,如果没有优化 std::vector 会浪费很多时间。使用 10M 个元素,它在 ~1,400 毫秒后完成,而仅在 ~80 毫秒后完成。那么,这是否意味着可以优化向量类以提供比动态数组更好的性能?
  • 我的系统上出现了相反的行为,可能是因为我正在运行大量其他东西。矢量 - 2203 和新的 - 63。
  • @Kristian:如果没有优化,每次访问向量都需要一个函数调用,这将比内联等效项慢几个数量级。它还可以检查索引是否在每次访问的范围内。通过优化,这两个版本应该会产生非常相似的代码。
  • 您使用的是什么操作系统(请参阅 Mike Seymour 的回答以了解其重要性)?

标签: c++ performance arrays optimization vector


【解决方案1】:

我的猜测是操作系统在第一次访问之前不会分配物理内存。 vector 构造函数将初始化所有元素,因此内存将在您开始计时时分配。数组内存未初始化(并且可能未分配),因此时间可能包括分配。

尝试将数组初始化更改为 int* intArray = new int[numElements](); 以对其元素进行值初始化,看看是否会改变结果。

【讨论】:

  • 好点。您认为第一次访问内存时发生的页面错误可以解释差异吗?我不了解 Windows,但在 Linux 上,第一次访问时会有效分配内存。
  • 你的疯狂猜测完全符合目标!这对括号现在给出了完全相同的性能。很好的收获。
  • @Alexandre:是的,如果没有分配物理内存,那么第一次访问将导致页面错误。操作系统将通过分配页面来处理这个问题(几千个,取决于平台的页面大小);它可能还必须将它们初始化为零。这将需要相当长的时间。我也不了解 Windows,但我有一个模糊的想法,它或多或少做着同样的事情。
【解决方案2】:

出于所有实际目的,以这种方式使用它们的速度完全相同。 vector 的 operator[] 通常是这样实现的 [MSVC version]:

const_reference operator[](size_type _Pos) const
{   // subscript nonmutable sequence
    return (*(_Myfirst + _Pos));
}

... 等同于:

const_reference operator[](size_type _Pos) const
{   // subscript nonmutable sequence
    return _Myfirst[_Pos];
}

您的测试基本上只是在测试您的编译器内联代码的能力,而且它似乎在这里做得很好。

至于对差异的解释,您得到的任何答案通常都是假设性的,没有看到反汇编。它可能与更好的缓存、第一种情况下更好地利用寄存器(尝试交换测试的顺序并看看会发生什么)等有关。值得注意的是,向量的内存将在测试开始之前被访问它将所有内容初始化为 ctor 中的 T() 的方式。

不幸的是,我们不能简单地编写像这样的小型微测试并从中得出一般性结论。在系统和优化编译器变得如此复杂之前,我们曾经能够更多地做到这一点,但现在涉及的变量太多,无法从实际测试之外的任何事情中得出有意义的结论。

出于同样的原因,我们通常希望任何认真对待性能的人主动分析他们的代码,因为事情变得过于复杂,以至于人们无法正确确定代码中的瓶颈,而没有明显的算法效率低下(我已经即使是对汇编和计算机体系结构有更深入理解的专业程序员也经常看到,当我使用分析器检查他们的假设时,我确实会出错)。

【讨论】:

  • 阿门最后一段。人工测试几乎没有用。找到真正的问题,进行基准测试并进行更改,直到基准有所改善。
【解决方案3】:

我刚刚做了这个实验。确实很奇怪的行为,虽然我想我明白了。

再次重复您的代码。那就是……

benchmark vector
benchmark array

benchmark vector
benchmark array

您会注意到第二次会得到不同的数字。我猜?页面错误。由于某种原因,向量不会导致页面错误,而数组方法会。加载页面后,两者将以大致相同的速度运行(即:第二次发生的情况)。有谁知道到目前为止如何打印进程中的页面错误数?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-12
    • 1970-01-01
    • 2019-11-07
    • 2012-04-18
    • 1970-01-01
    • 2020-03-23
    • 2020-08-17
    相关资源
    最近更新 更多