【问题标题】:Which one is better stl map or unordered_map for the following cases对于以下情况,哪个更好 stl map 或 unordered_map
【发布时间】:2012-09-04 23:57:56
【问题描述】:

我正在尝试比较某些操作的 stl map 和 stl unordered_map。我在网上看了看,它只会增加我对哪个整体更好的怀疑。所以我想根据它们执行的操作来比较两者。

哪个表现更快

插入、删除、查找

哪一个需要更少的内存和更少的时间从内存中清除。任何解释都受到热烈欢迎!!!

提前致谢

【问题讨论】:

  • 一个通常具有对数复杂度,另一个具有恒定(摊销)复杂度。您的密钥是什么数据类型?它的哈希函数有多快?
  • @ildjarn 我正在寻找字符串类型的键。

标签: c++ visual-c++ stl


【解决方案1】:

插入、删除、查找中哪一个执行得更快?哪一个需要更少的内存和更少的时间来从内存中清除它。任何解释都受到热烈欢迎!!!

对于特定用途,您应该尝试使用您的实际数据和使用模式,看看哪个实际上更快......有足够的因素假设其中任何一个总是“获胜”是很危险的。

无序映射/哈希表的实现和特点

在学术上 - 随着元素数量向无穷大增加,std::unordered_map(这是 C++ 库提供的计算科学术语“哈希图”或“哈希表”)上的操作将倾向于继续占用相同的时间量 O(1)(忽略内存限制/缓存等),而对于 std::map(平衡二叉树),每次元素数量翻倍时,它通常需要进行额外的比较操作,所以它逐渐变慢 O(log2n)。

std::unordered_map implementations necessarily use open hashing:基本的期望是会有一个连续的“桶”数组,每个在逻辑上都是一个包含任何散列到其中的值的容器。

它通常用于将哈希表描绘为vector<list<pair<key,value>>>,其中从向量元素获取值涉及至少一个指针解引用,因为您遵循存储在存储到初始列表节点;插入/查找/删除操作的性能取决于列表的大小,平均等于unordered_mapload_factor

如果max_load_factor 降低(默认值为 1.0),那么在插入过程中会发生更少的冲突,但会发生更多的重新分配/重新散列和更多的内存浪费(这会通过增加缓存未命中来影响性能)。

这个最明显的unordered_map 实现的内存使用涉及bucket_count() list-head-iterator/pointer-sized 桶的连续数组和每个键/值对的一个双向链表节点。通常,bucket_count() + 2 * size() 额外的开销指针,针对实现可能执行的动态内存分配请求大小的任何四舍五入进行了调整。例如,如果您要求 100 个字节,您可能会得到 128、256 或 512。实现的动态内存例程也可能使用一些内存来跟踪已分配/可用区域。

不过,C++ 标准为实际实现留出了空间,让他们自己做出一些性能/内存使用决策。例如,他们可以在分配一个新的更大的数组后将旧的连续存储桶数组保留一段时间,因此可以逐渐将值重新散列到后者中,以降低最坏情况下的性能,但会以平均情况性能为代价在操作过程中会查询这两个数组。

maps/平衡二叉树的实现及特点

map 是二叉树,可以预期使用指针链接不同的堆内存区域,这些区域由对new 的不同调用返回。除了键/值数据外,树中的每个节点都需要父、左和右指针(如果丢失,请参阅wikipedia's binary tree article)。

比较

因此,unordered_mapmap 都需要为键/值对分配节点,前者通常具有两个指针/迭代器开销用于上一个/下一个节点链接,而后者具有三个用于父/左/正确的。但是,unordered_map 还具有用于 bucket_count() 存储桶的单个连续分配 (== size() / load_factor())。

对于大多数用途而言,内存使用量的差异并不大,而且一个额外区域的释放时间差异不太可能引起注意。

另一种选择

对于那些预先填充容器然后重复搜索而无需进一步插入/擦除的情况,有时使用排序向量可能是最快的,使用标准算法搜索binary_searchequal_rangelower_bound、@987654329 @。这具有单个连续内存分配的优点,这对缓存更加友好。它总是优于 map,但 unordered_map 可能仍然更快 - 如果你在乎,请测量。

【讨论】:

  • 不明白为什么会这样: 基本的期望是会有一个连续的键/值“桶”数组。或者这有什么帮助,因为我一直期望存储桶存储区域大于任何内部缓存,并且因为以给定顺序遍历元素可能会给您完全随机的存储桶缓存位置不太可能是一个因素。
  • @LokiAstari:“不明白为什么 [...] 会是一个期望”带有(键/值)对 - 类似的东西是我的“基本期望”,也是一个有用的出发点,因为添加了实现细节,例如桶包含键/值的链表、存储在桶中的空闲列表或锁等,多个连续数组以加速容量扩展。
【解决方案2】:

地图:

插入:

  1. 对于第一个版本( insert(x) ),对数。
  2. 第二次 版本( insert(position,x) ),通常是对数,但是 如果在指向的元素之后立即插入 x,则摊销常数 按位置。
  3. 对于第三个版本( insert (first,last) ), Nlog(size+N) 通常(其中 N 是第一个和 最后,并在插入之前调整容器的大小),但是 如果第一个和最后一个之间的元素已经排序,则为线性 根据容器使用的相同排序标准。

删除:

  1. 对于第一个版本(擦除(位置)),摊销常数。
  2. 对于第二个版本 ( erase(x) ),容器大小为对数。
  3. 对于最后一个版本(erase(first,last)),容器大小的对数加上第一个和最后一个之间的距离的线性。

查找:

  1. 大小为对数。

无序地图:

插入:

  1. 单元素插入:
    1. 平均情况:常数。
    2. 最坏情况:容器大小呈线性关系。
  2. 多个元素插入:
    1. 平均情况:插入的元素数量呈线性关系。
    2. 最坏情况:N*(size+1):插入的元素数乘以容器大小加一。

删除:

  1. 平均情况:移除的元素数量呈线性关系(仅移除一个元素时保持不变)
  2. 最坏情况:容器大小呈线性关系。

查找:

  1. 平均情况:常数。
  2. 最坏情况:容器大小呈线性关系。

知道了这些,你就可以根据实现的类型来决定使用哪个容器了。

来源:www.cplusplus.com

【讨论】:

    【解决方案3】:

    两者兼有的原因是两者都不是整体更好。

    使用任何一种。如果另一个证明更适合您的使用,请切换。

    • std::map 为更糟糕的时间提供更好的空间。
    • std::unordered_map 为更差的空间提供了更好的时间。

    【讨论】:

    • @LokiAstari 我不认为这种权衡是由标准保证的。难道不能想象一个实现可以提供比map更好的内存使用的unordered_map吗?
    • @AndrewDurward:是的,总会有这种可能性。但在一般情况下,你是在用空间换时间。
    【解决方案4】:

    您的问题的答案在很大程度上取决于您使用的特定 STL 实现。确实,您应该查看 STL 实现的文档——它可能包含大量有关性能的信息。

    不过,一般来说,根据 cppreference.com,maps 通常实现为 red-black trees 并支持时间复杂度为 O(log n) 的操作,而 unordered_maps 通常支持恒定时间操作。 cppreference.com 几乎没有提供关于内存使用情况的信息;但是,another StackOverflow answer 建议地图通常会比 unordered_maps 使用更少的内存。

    对于带有 Visual Studio 2012 的 Microsoft 包的 STL 实现,看起来map 在摊销 O(log n) 时间内支持这些操作,而unordered_map 在摊销常数时间内支持它们。但是,文档没有明确说明内存占用。

    【讨论】:

    • “您的问题的答案在很大程度上取决于您使用的特定 STL 实现”不,不是。标准要求复杂性保证。它们不是可选的。
    • @NicolBolas:复杂性保证只是其中的一半——OP 还询问了内存使用情况。
    • 复杂度保证不到一半。恒定因素在实践中很重要。并且该标准基本上没有说明小型集和地图的性能。性能复杂。
    • @JasonOrendorff:小集合/地图的性能不太可能成为瓶颈。大型集/地图的性能是。因此,大 O() 符号对此很有用。这使得常数因素变得不那么重要(所以不到一半)。复杂性保证了一半以上。否则,如果它们像您建议的那样重要,标准会提到它们。
    • @LokiAstari 一个应用程序可能使用许多小集合和地图,然后它们的性能可能很重要。当然,在 Python 中,实际程序中的绝大多数 dicts 都非常小。如此之多,以至于 dict 代码包含针对小表的特殊性能技巧。
    猜你喜欢
    • 2016-12-24
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    • 2012-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-29
    相关资源
    最近更新 更多