【问题标题】:Performance difference for iteration over all elements std::unordered_map vs std::map?迭代所有元素 std::unordered_map 与 std::map 的性能差异?
【发布时间】:2019-06-30 14:26:50
【问题描述】:

我想以指针为键映射数据。我应该选择什么容器,map 还是 unordered_map?这个主题有多个关于 stackoverflow 的问题,但没有一个涉及我们需要迭代所有键值对时的性能方面。

std::map<classKey* , classData*> myMap;
std::unordered_map<classKey* , classData*> myUnorderedMap;

for (auto & iter : myMap) { //loop1
    display(iter.second);
}

for (auto & iter : myUnorderedMap) { //loop2
    display(iter.second);
}

loop1 vs loop2 哪一个提供更好的性能。 Bench Mark由@RetiredNinja提供

对于 size = 10,000,000 我们得到以下基准测试结果:

【问题讨论】:

  • 有趣。关了我不知道。不过,我会把钱放在std::map 上。你能设置一些用于分析的测试用例吗?
  • 请展示您如何对此进行基准测试。
  • @MichaelChourdakis 再次,OP 询问的是性能,而不是时间复杂度方面的效率。
  • 不确定我是否正确使用了这个快速基准测试工具,但玩起来很有趣。 quick-bench.com/sxQWkAg6tXRWsUCXIArv4NbjZ3E
  • Quick Bench 非常出色; have used it to great effect(如果我自己这么说的话)

标签: c++ c++11 stl


【解决方案1】:

如您所料,这在很大程度上取决于标准库数据结构的实际实现。因此,这个答案将更具理论性,并且与任何一种实现的联系都更少。

std::map 在后台使用平衡二叉树。这就是为什么它有 O(log(n)) 的插入、删除和查找。对其进行迭代应该是线性的,因为您只需要进行深度优先遍历(这将需要 O(log(n)) 以堆栈空间形式存在的内存)。使用 std::map 进行迭代的好处是您将按排序顺序迭代键,并且您将“免费”获得该好处。

std::unordered_map 在幕后使用哈希表。这使您可以进行摊销的常数时间插入、删除和查找。如果实现没有针对迭代进行优化,一种简单的方法是迭代哈希表中的每个桶。由于一个好的哈希表(理论上)在 50% 的桶中恰好有一个元素,而在其余的桶中则为零,所以这个操作也将是线性的。但是,与std::map 的相同线性运算相比,它会花费更多的“挂钟时间”。为了解决这个问题,一些哈希表实现保留了所有元素的边列表,以便快速迭代。如果是这种情况,在 std::unordered_map 上进行迭代会更快,因为没有比在连续内存上迭代更好的了(但显然仍然是线性时间)。

在极不可能的情况下,您实际上需要优化到这个级别(而不仅仅是对理论上的性能感到好奇),您的代码中其他地方可能会遇到更大的性能瓶颈。

所有这些都忽略了键控指针值的奇怪之处,但这既不是这里也不是那里。

进一步阅读的来源:

GCC std::map implementation

GCC std::unordered_map implementation

How GCC std::unordered_map achieves fast iteration

【讨论】:

  • "std::map 在后台使用平衡二叉树", "std::unordered_map 在后台使用哈希表。" 这些说法有可靠的来源吗?
  • 诚然,这是一个宽泛的声明,因为标准中没有明确要求这些设计选择。但是,GCC 和 MS VS 编译器都以这种方式实现它们(大多数知名编译器也是如此)。
  • @πάνταῥεῖ 人类(到目前为止)无法提出更好的实现来满足标准的要求并且没有禁忌症以这种方式实现结构,对我来说,足以假设这一点(或接近这)实施。是的,标准没有说明具体的实现,但我们还能得到什么?
  • 您的最后一个消息来源说哈希映射将迭代器(= 指针链接列表保存到存储桶。那根本不连续(2个间接)。只有水桶会。而且没有理由地图不能对节点做同样的事情。我真的很想看到一些测量作为答案。
  • “迭代它应该是线性的,因为你只需要进行深度优先遍历(这将需要 O(log(n)) 以堆栈空间形式存在的内存)。” - 这不是std::map 迭代器的工作方式。它们是外部迭代器,不能分配任何堆栈空间。递增树迭代器只是摊销恒定时间。 (遍历整个树仍然是线性的。)请参见此处:github.com/gcc-mirror/gcc/blob/…
猜你喜欢
  • 2021-12-23
  • 2016-03-19
  • 1970-01-01
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 2022-01-13
  • 1970-01-01
  • 2011-01-21
相关资源
最近更新 更多