在 C++ std::unordered_map 中预分配桶

Posted

技术标签:

【中文标题】在 C++ std::unordered_map 中预分配桶【英文标题】:Pre-allocating buckets in a C++ std::unordered_map 【发布时间】:2011-08-19 19:09:08 【问题描述】:

我正在使用来自 gnu++0x 的std::unordered_map 来存储大量数据。我想为大量元素预先分配空间,因为我可以限制使用的总空间。

我想做的是调用:

std::unordered_map m;
m.resize(pow(2,x));

其中 x 是已知的。

std::unordered_map 不支持这个。如果可能的话,我宁愿使用std::unordered_map,因为它最终会成为标准的一部分。

其他一些限制:

需要可靠的 O(1) 访问和地图变异。所需的散列和比较函数已经是非标准的并且有些昂贵。 O(log n) 突变(如std::map)太贵了。

-> 昂贵的哈希和比较也使得基于摊销的增长方式过于昂贵。每个额外的插入都需要对这些函数进行 O(n) 次操作,这会导致算法运行时间中出现额外的二次项,因为指数存储需求需要 O(n) 次增长。

【问题讨论】:

【参考方案1】:
m.rehash(pow(2,x));

如果pow(2, x) 是您想要预分配的桶数。您还可以:

m.reserve(pow(2,x));

但现在pow(2, x) 是您计划插入的元素数量。这两个函数除了预先分配桶外什么都不做。他们不插入任何元素。它们都旨在完全用于您的用例。

注意:不能保证您得到准确的 pow(2, x) 存储桶。一些实现将仅使用 2 的幂的桶数。其他实现将仅使用质数的桶。还有一些人将只使用素数的一个子集来表示桶的数量。但在任何情况下,实现都应该以您想要的桶数接受您的提示,然后在内部四舍五入到下一个可接受的桶数。

这是最新版 (N4660) 用于指定 rehash 参数的准确措辞:

a.rehash(n) : 后置条件: a.bucket_count() >= a.size() / a.max_load_factor() and a.bucket_count() >= n.

此后置条件确保bucket()_count() >= nload_factor() 保持小于或等于max_load_factor()

随后reserve(n) 定义为rehash(n)

a.reserve(n) :与a.rehash(ceil(n / a.max_load_factor())) 相同。

【讨论】:

你正在使用提示,就好像它在:iterator std::set::insert(iterator hint, const value_type& value); en.cppreference.com/w/cpp/container/set/insert,看起来措辞不当。 @Manohar 不,他正在使用“提示”作为美国英语中该词的口语含义。意思很清楚。 @MikeBorkland 我在谈论 C++ 概念时提到了这个词的斜体。【参考方案2】:

我认为无序映射具有预先分配的内存并不重要。 STL 预计为 O(n) 摊销插入时间。在我看来,在您知道这是您的代码的瓶颈之前,您可以省去编写自己的分配器的麻烦。

【讨论】:

STL 保证 O(n) 摊销插入时间,但实现这一点的常用方法是将桶数增加一个常数因子,然后重新散列每个现有元素。如果您在地图中存储 n 个元素,则会发生 O(log n) 次。当 n 为 2^large 时,这会为执行的插入次数增加一个额外的大因子。我正在努力消除这个因素。 "这增加了一个额外的因子 large" 不,它增加了一个额外的因子 2。您了解摊销操作的工作原理吗?这个答案错误的唯一真正原因是因为它不“保证” O(n) 摊销插入时间,它只提供预期的 O(n) 摊销插入时间,在随机插入的元素上具有指数高的概率。如果您知道桶将调整到的确切大小以及将使用的哈希函数,则仍然有可能欺骗哈希表并在每次插入时强制发生 N 次冲突。 除非你试图做一些不分配的事情——例如在一个实时线程中。在这种情况下很重要。【参考方案3】:

构造函数根据http://en.cppreference.com/w/cpp/container/unordered_map/unordered_map取参数“size_type bucket_count”

所以按照您的示例代码所说的最简单的方法是:

std::unordered_map m pow(2,x) ;

这将更有效,因为未定义在构造时将保留多少桶,否则,它可能必须在您之后调用 Reserve 时分配然后解除分配。

【讨论】:

【参考方案4】:

我认为 rehashreserve 只有在您事先知道映射值将占用多少内存时才有效。如果映射的值很复杂或大小动态变化(例如向量),您将需要自己的实现。例如,如果您的内存大小允许,您可以保留可能存在的最大容器。

【讨论】:

您提出的某些观点没有意义,或者您没有让自己理解。例如“如果映射值动态变化是大小(例如向量)”。无论向量中有多少元素(或任何容器或类),sizeof(std::vector<T>) 保持不变(显然是相同的T)。 map 将为 1 个元素的 std::vector 或 1 mil 的 std::vector 保留确切的空间量。 “您可以保留可能碰巧存在的最大容器”是我认为在这个问题的上下文中不是一个合理的建议的另一点。【参考方案5】:

我建议您为 std::unordered_map 编写您自己的分配器,它可以完全按照您想要的方式分配内存。

【讨论】:

以上是关于在 C++ std::unordered_map 中预分配桶的主要内容,如果未能解决你的问题,请参考以下文章

std::unordered_map 如何表现? [C++]

C++ 将预先保留的哈希映射(std::unordered_map)与整数键和连续数据数组(std::vector)进行比较

C++ std::unordered_map怎么用

Javascript中C++ std::unordered_map<char, int> 的等价物是啥?

C++ std::map 和 std::unordered_map 区别 时间复杂度 适用

与 C++ unordered_map 的并行性