矢量vs地图表现混乱

时间:2014-07-02 23:58:54

标签: c++ stdvector

编辑:我专门将std::vector线性搜索操作与std::map 二进制搜索操作进行比较,因为这是Herb声称的似乎涉及到。我知道使用二进制搜索会将性能从O(N)移到O(log N),但这不会测试Herb的声明

Bjarne Stroustrup和Herb Sutter最近都谈到了在人们期望使用std::vector的情况下有多棒std::list,这是由于链表遍历期间缓存未命中的代价。 (见48分钟http://channel9.msdn.com/Events/Build/2014/2-661

Herb进一步声明,然而对有序向量的操作甚至比std::map更快,(参见http://i.imgur.com/zX07TZR.png取自上述频道9视频的51:30标记),我发现很难捉摸。所以我创建了一个小测试来证明这一点并且很难再现这些结果:https://ideone.com/MN7DYK

这是测试代码:

template <typename C>
void test(std::string name, std::vector<int> shuffledInputValues, C & c)
{
   // fill container 'c' with values from 'shuffledInputValues' then erase them all
   {
      std::cout << "testing " << name << "..." << std::endl;
      timer t;

      for (auto val : shuffledInputValues) insert(c, val);
      for (auto val : shuffledInputValues) remove(c, val);
  }
}

// output:
// testing vector...99.189ms
// testing deque...120.848ms
// testing set...4.077ms

请注意 std::vector的执行速度比std::set 慢一个数量级。当然这是我预期的结果,但我对Herb试图制作的说法感到困惑。

我做错了什么?还是我误解了赫伯的主张?

关于我的测试应用的说明:

  • 它必须使用线性操作 - 练习的全部内容是演示CPU缓存魔法,这些是Herb和Bjarne对练习的约束
  • 我没有为我的矢量迭代尝试任何棘手的循环解开,但我相信迭代不会影响性能很多
  • 我将循环限制为10K项,因为ideone在较大的集上超时,但增加大小不会改变结果

编辑:请参阅https://ideone.com/916fVd以获取仅比较查找性能的修改示例。线性搜索表现出相同的性能。

2 个答案:

答案 0 :(得分:8)

我找到slides以便于参考(我看不到图表,但我想这可能是因为专有文件格式)。相关幻灯片是第39号,描述了正在解决的问题:

  

§生成N个随机整数并将它们插入序列中,以便按数字顺序将每个整数插入到正确的位置。

     

§通过在序列中选择一个随机位置并删除那里的元素,一次删除一个元素。

现在,很明显链接列表不是这个问题的好选择。即使列表在开头或中间插入/删除比使用矢量要好得多,但由于需要线性搜索,因此在随机位置插入/删除不好。由于更好的缓存效率,使用向量的线性搜索速度更快。

Sutter建议地图(或一般的树)似乎是此算法的自然选择,因为您获得了O(log n)搜索。事实上,对于插入部分中的大N值,它确实很容易击败向量。

但是。您需要删除第n个元素(对于随机n)。这是我认为你的代码作弊的地方。您可以按插入顺序删除元素,有效地使用输入向量作为查找表,以便在“随机”位置查找元素的值,以便您可以在O(log n)中搜索它。所以你真的使用set和vector的组合来解决问题。

常规二进制搜索树,例如用于std::mapstd::set(我假设使用Sutter)的树,没有用于查找第n个元素的快速算法。据称Here's one平均为O(log n),最坏情况下为O(n)。但是std::mapstd::set不提供对底层树结构的访问权限,因此对于那些你仍然按顺序遍历的人(如果我错了就纠正我),这又是一次线性搜索!我真的很惊讶地图版本与Sutter的结果中的矢量竞争对手。

对于log(n)复杂性,您需要一个Order statistic tree这样的结构,遗憾的是标准库不提供该结构。如here所示,存在基于GNU策略的STL MAP。

这是我为vector vs set vs ost制作的快速测试代码(对于带有二分搜索的矢量用于良好测量)https://ideone.com/DoqL4H 设置慢得多,而另一个基于树的结构比矢量快,这与Sutter的结果不一致。

order statistics tree: 15.958ms
vector binary search: 99.661ms
vector linear search: 282.032ms
set: 2004.04ms

(N = 20000,差异只会更大,有利于更大值的ost)

简而言之,我得出的结论是萨特的原始结果看起来很奇怪,但原因略有不同。在我看来,这次更好的渐近复杂性赢得了更低的常数因子。

请注意,问题描述并不排除重复随机值的可能性,因此使用map / set而不是multimap / multiset会对地图/集合有所作为,但我认为其意义不大当值域远大于N时。此外,预先保留矢量并不会显着提高性能(当N = 20000时,约为1%)。

答案 1 :(得分:-1)

在没有源代码以及有关编译器选项,硬件等的信息的情况下,当然很难给出准确的答案。

一些可能的差异:

  • 我认为发言人每次都在讨论向量的中间中的插入/删除(而你总是添加到结尾并从示例中的开头删除);
  • 发言者没有提到如何确定添加/删除哪些项目,但在这种情况下我们也可以假设最小努力来确定这一点:每次都进行向量访问,而插入值可以简单地计算(例如使用低成本PNRG来确定要插入的下一个值)或者总是相同,并且为了移除,每次都移除中间元素,因此不需要查找/计算值。

然而,正如另一位评论员所说,我会取消一般原则,而不是具体的数字/时间。从本质上讲,外卖信息是:您认为自己知道的事情&#34;计算操作&#34;为了评估算法性能/可扩展性,现代系统已不再适用。