【问题标题】:C++ Vector vs Array (Time)C++ 向量与数组(时间)
【发布时间】:2010-12-28 06:45:40
【问题描述】:

我在这里有两个程序,它们都在做完全相同的任务。他们只是将布尔数组/向量设置为真值。使用向量的程序 运行需要 27 秒,而涉及大小为 5 倍的数组的程序需要不到 1 秒。我想知道为什么会有如此大的差异的确切原因?是向量 真的那么低效吗?

使用向量编程

#include <iostream>
#include <vector>
#include <ctime>

using namespace std;

int main(){
 const int size = 2000;
 time_t start, end;
 time(&start);
 vector<bool> v(size);
 for(int i = 0; i < size; i++){
  for(int j = 0; j < size; j++){
   v[i] = true;
  }
 }
 time(&end);
 cout<<difftime(end, start)<<" seconds."<<endl;
}

运行时间 - 27 秒

使用数组编程

#include <iostream>
#include <ctime>

using namespace std;

int main(){
 const int size = 10000; // 5 times more size
 time_t start, end;
 time(&start);
 bool v[size];
 for(int i = 0; i < size; i++){
  for(int j = 0; j < size; j++){
   v[i] = true;
  }
 }
 time(&end);
 cout<<difftime(end, start)<<" seconds."<<endl;
}

运行时间 -

平台 - Visual Studio 2008 操作系统 - Windows Vista 32 位 SP 1 处理器 Intel(R) Pentium(R) 双 CPU T2370 @ 1.73GHz 内存 (RAM) 1.00 GB

谢谢

亲爱的

【问题讨论】:

  • std::vector 不是容器。阅读:gotw.ca/publications/mill09.htm
  • 重要提示:即使您得出正确的结论,但您并没有进行正确的比较。您执行最内层循环的 N^2 次迭代(v[i] = true 语句),但 N 在一个测试中为 2000,在另一个测试中为 10000,因此您实际上做了 25 倍的工作,而不是 5 倍从 vector 和普通数组之间的区别。这实际上使差异更加明显。
  • @user235022 你的意思是v[j] = true; 而不是v[i] = true?否则编译器优化内部循环输出应该非常简单,因为您的操作不依赖于循环变量。

标签: c++ arrays time vector performance


【解决方案1】:

您使用的是 std::vector of bool ,这不是您认为的那样!

bool 的向量是一个不应该存在的混蛋子模板特化,实际上每个位都存储了 1 个 bool。由于屏蔽和移位逻辑,对它的访问更加复杂,因此肯定会慢一些。

Click here for some info on vector of bool.

此外,您可能正在运行未优化的构建(几乎可以肯定,考虑到您列出的时间,27 秒对于 400 万次迭代来说太离谱了)。标准模板库非常依赖优化器来执行内联函数调用和省略临时函数等操作。缺乏这种优化会导致 bool 向量的性能下降特别严重,因为当你索引它时它必须返回一个代理对象,因为你不能获取位的地址,所以 operator [] 无法返回引用。

Click here for more info on proxied containers(后半部分是关于bool的向量)

此外,许多 STL 实现都有有用的调试位,这些调试位不是标准的一部分,可帮助您捕获错误,但确实会降低性能。您需要确保在优化的构建中禁用这些。

一旦您打开优化器,进行正确的设置(即没有打开 STL 调试),并且实际上在两个循环中测试相同的东西,您将几乎看不到任何区别。

为了在我的机器上测试,我不得不让你的循环更大,但这里有两个构建的 bool 循环向量在我的机器上,显示了优化器标志对 STL 代码的影响

$ g++ main.cpp 
$ ./a.out 
17 seconds.
$ g++ -O2 main.cpp 
$ ./a.out 
1 seconds.

【讨论】:

  • 是的,我也这么认为。我运行相同的场景,花费的时间几乎相同。
  • VC2005+ 特别为所有 STL 对象的调试版本提供了边界检查和迭代器验证检查。
  • 感谢 don.neufeld,您的解释和链接真的很有帮助。很高兴学习新东西:-)
  • 你也应该试试std::deque&lt;bool&gt;,它不会受到向量产生的模板专业化错误的影响。
【解决方案2】:
【解决方案3】:

std::vector&lt;bool&gt; 针对内存消耗而非性能进行了优化。

您可以使用std::vector&lt;int&gt; 来欺骗它。那么你不应该有性能缺陷。

【讨论】:

  • 修复了您的帖子以使用代码格式。没有它,尖括号就消失了
  • 我建议使用vector&lt;char&gt;(或unsigned char,或者如果编译器支持std::uint8_t)而不是vector&lt;int&gt;。没有理由使用比您需要的更多的空间。但绝对不是vector&lt;bool&gt;
  • 使用更多空间的原因是在大多数 32 位架构上速度更快。
  • intchar 之间的速度差异可能不值得使用 4 倍以上的内存(尤其是因为缓存未命中造成的伤害更大)。
【解决方案4】:

其他的答案都很好,不过你可以通过this method自己轻松回答。

添加:作为对 cme​​ts 的回应,让我告诉你我的意思。我在 Windows 上运行 VC,但这适用于任何语言/操作系统。我采用了您的第一个程序并将大小增加到 20000,这样它就可以运行足够长的时间。然后在它运行时,我拍了几张照片。它们看起来都是这样的:

std::vector<bool,std::allocator<bool> >::begin() line 93 + 25 bytes
std::vector<bool,std::allocator<bool> >::operator[]() line 132 + 37 bytes
main() line 24 + 12 bytes
mainCRTStartup() line 206 + 25 bytes
KERNEL32! 7c817077()

也就是说,它基本上所有时间都花在了第 24 行的索引操作上,而花时间的原因是 [] 运算符正在调用 @ 987654324@ 运营商。更详细:

main() line 24 + 12 bytes

是这个代码吗:

for(int j = 0; j < size; j++){
==> v[i] = true;
}

调用:

std::vector<bool,std::allocator<bool> >::operator[]() line 132 + 37 bytes

这是这段代码(我重新格式化了一下):

reference operator[](size_type _P){
==> return (*(begin() + _P));
}

调用:

std::vector<bool,std::allocator<bool> >::begin() line 93 + 25 bytes

这是在做什么(更详细):

92:       iterator begin()
93:           {return (_First); }
00402890   push        ebp
00402891   mov         ebp,esp
00402893   sub         esp,44h
00402896   push        ebx
00402897   push        esi
00402898   push        edi
00402899   push        ecx
0040289A   lea         edi,[ebp-44h]
0040289D   mov         ecx,11h
004028A2   mov         eax,0CCCCCCCCh
004028A7   rep stos    dword ptr [edi]
004028A9   pop         ecx    <===============
004028AA   mov         dword ptr [ebp-4],ecx
004028AD   mov         eax,dword ptr [ebp-4]
004028B0   mov         eax,dword ptr [eax+4]
004028B3   pop         edi
004028B4   pop         esi
004028B5   pop         ebx
004028B6   mov         esp,ebp
004028B8   pop         ebp
004028B9   ret

它所做的是在堆栈上写入 68 个字节的0xCC(出于某种调试原因),作为获取向量的begin 地址的一部分,作为计算v[i] 地址的一部分,之前做任务。

它花费在此操作上的时间比例接近 100%,因为它对所采集的多个样本中的每一个都执行此操作。你能猜到这就是它几乎所有时间都在做的事情吗?我不能。

当然,这是一个调试版本。如果您切换到 Release 版本,但打开调试信息,所有这些函数都会被内联和优化,因此速度会快 30 倍,而且堆栈快照再次准确地说明了它在做什么。

所以 - 人们可以告诉你它可能在做什么,但这表明如何找出你自己真正在做什么.

在您的环境中无疑会有所不同。

【讨论】:

  • 确实如此。与其了解标准库数据结构的属性,不如让他了解如何在他实际使用的其他操作系统中分析您的代码。而且,如果您曾经尝试过调试、分析或以其他方式阅读标准库容器,您就会知道它并不完全易于阅读。分析可能会告诉您哪些代码行导致速度变慢,但它可能无法回答 发生了什么的问题。
  • @jalf:来吧。它独立于操作系统,并且通常理解的分析可能无法告诉您发生了什么,但只要有源代码,stackshots 就会确切地告诉您发生了什么图书馆。
  • ...这是老生常谈的关于给某人一条鱼而不是教他们钓鱼的说法。
猜你喜欢
  • 1970-01-01
  • 2017-01-19
  • 1970-01-01
  • 2016-05-15
  • 2019-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-18
相关资源
最近更新 更多