std::vector 或 std::list 用于 std::unordered_map 存储桶?
Posted
技术标签:
【中文标题】std::vector 或 std::list 用于 std::unordered_map 存储桶?【英文标题】:std::vector or std::list for std::unordered_map buckets? 【发布时间】:2014-03-15 21:05:47 【问题描述】:当两个键映射到std::ordered_map<T>
中的同一个桶时,首选的数据结构是什么。我不确定使用std::vector<T>
(在达到容量但快速迭代时需要复制所有元素)或std::list<T>
是否会更好,这很容易添加新元素但迭代速度较慢?
std::vector
快速迭代元素 如果容量用完,因为需要将所有元素复制到新内存中,那就不好了std::list
快速添加/删除节点 不适合作为不在连续内存中的元素进行迭代【问题讨论】:
答案取决于使用模式。 你漏掉了std::deque
。
【参考方案1】:
即使不知道您的确切使用模式和工作量,我也会说std::vector
更好。大多数情况下都是如此,我将在下面尝试解释原因。
在大多数情况下,您对哈希表的查找比插入要多得多。查找需要对桶进行迭代,插入需要添加元素并可能调整大小。因此,针对更常见的用例进行优化更有意义。
内部的每个插入都需要进行查找,因此您的查找至少与插入一样多;通常更多。
大多数情况下,每个桶的平均键数很少。这转化为使用小向量与小列表。并且调整小向量的大小(涉及复制元素)会很快。
矢量“调整大小”通常不会经常发生,因此您不必无理地害怕它。 (尽管您应该注意,当矢量较小时,调整大小确实会更频繁地发生。这对于我所知道的所有实现都是如此,但纠正/规避也是微不足道的。)
向量迭代比列表迭代快很多。 很多。
即使调整和复制(或移动)向量的速度也可以与将元素添加到列表中一样快(或几乎一样快)。
在您的数据类型中提供适当的“移动”支持,调整向量大小的开销变得更小。
您可以使用向量预先分配一些元素,并且几乎完全消除了桶中的所有调整大小。例如,如果您知道 99% 的存储桶将包含 3 个或更少的键,您可以为每个向量保留 3 或 4 个元素,而无需考虑调整大小(几乎)。
向量(特别是当它们的元素类型较小时)比链表更节省空间。 std::list
需要为每个元素保留两个额外的指针,这可能是一个巨大的开销(8-16 字节的元素为 50%-200%,具体取决于您是 32 位还是 64 位。)
由于它们的尺寸更小且在内存中具有连续性,向量通常是一种更快、更好的数据结构。
最终,您必须在自己的代码库中并根据自己的工作负载和使用模式进行自己的测量和基准测试。如果没有完整的信息,没有人可以给你一个明确的答案。因此,如果您的元素是非常大的、不可移动的对象,并且您主要进行插入/删除并且很少进行查找,那么请继续使用链表。否则,使用向量。
你可以看看this benchmark,比较vector、list和deque。它可能会进一步帮助您决定使用矢量!
【讨论】:
RE 点 4 和 9:这些讨论的是一般情况,但对于存储桶,我们通常处理每个存储桶 0 到 size_ts),它是恒定的,但不会在许多存储桶条目中摊销。 @delnan:你当然是对的。我将编辑我的答案以包含您的观点。但是你和我一样知道向量(或更一般地说,一个类似数组的容器)对于不能或不会做自己的案例特定基准测试的人来说是正确的后备选择。我觉得太多的细节会不公平和错误地将它们偏向向量!我实际上是在这里为数组和向量传福音! @delnan:关于您关于向量空间开销的观点,链表具有相同的开销。根据实现,链表可能存储两个指针(指向前和后)甚至一个大小。当然,它可能只是指向前面的单个指针(并且列表将是循环的),但无论如何,列表也具有相当的空间开销。 我绝对同意应该使用vector
或其他具有连续存储的容器。但是对于一两个项目,它可能并不比链表更多节省空间。以上是关于std::vector 或 std::list 用于 std::unordered_map 存储桶?的主要内容,如果未能解决你的问题,请参考以下文章
std::vector::insert 与 std::list::operator[]
为啥从 std::vector 中随机删除比 std::list 快?
指向 std::vector 和 std::list 元素的指针