【问题标题】:Why does std::unordered_map have a reserve method?为什么 std::unordered_map 有保留方法?
【发布时间】:2017-07-11 10:11:31
【问题描述】:

根据this,您不能为std::map预留空间:

不,地图的成员在内部存储在树结构中。 除非您知道键和值,否则无法构建树 要存储的。

从这里可以明显看出为什么std::map 会缺少reserve() 方法,它在cppreference.com 上就是这样做的。然而,std::unordered_map 确实 有一个reserve() 方法,但是当我尝试将它与operator[]insert()emplace() 一起使用时,尽管我调用了@,它们都会去分配内存987654330@先。

这是怎么回事?为什么reserve() 不能正确保留所需的空间?如果和map一样不能事先分配内存,那为什么std::unordered_map一开始还有reserve()方法呢?

【问题讨论】:

  • 您能描述一下您用来确定 operator[]、insert() 或 emplace() 分配内存而不是使用 .reserve() 预留的内存的方法吗?
  • @nos 我在每次通话中都通过了程序集,它们都以某种List_buy()BuyNode() 通话结束,最终调用operator new(),然后是malloc()

标签: c++ memory visual-c++ stdmap


【解决方案1】:

unordered_map 容器有一个reserve 方法,因为它是使用存储桶实现的,而不是map 中的树。

一个桶是:

容器内部哈希表中的一个槽,元素根据其键的哈希值分配到该槽。桶的编号从 0 到 (bucket_count-1)。 (source)

单个存储桶包含可变数量的项目。此数字基于load_factor。当load_factor达到一定的阈值时,容器会增加bucket的数量并对map进行rehash。

当您调用 reserve(n) 时,容器会创建足够的存储桶以至少容纳 n 项。

这与rehash(n) 不同,后者直接将桶数设置为n 并触发整个哈希表的重建。

另见:Pre-allocating buckets in a C++ unordered_map

根据评论进行编辑

由于我不知道 cmets 提出的问题的确切答案,而且我的初步研究没有证明有成果,所以我决定进行实验测试。

作为参考,问题归结为:

您能否解释一下为 n 个元素保留存储桶是否与为 n 个元素分配内存相同?

根据this answer,准确检索unordered_map 中分配空间的大小是棘手且不可靠的。所以我决定使用 Visual Studio 2015 的诊断工具。

首先,我的测试用例如下:

#include <unordered_map>
#include <cstdint>

struct Foo
{
    Foo() : x(0.0f), y(0.0f), z(0.0f) { }

    float x;
    float y;
    float z;
};

int32_t main(int32_t argc, char** argv)
{
    std::unordered_map<uint32_t, Foo> mapNoReserve;
    std::unordered_map<uint32_t, Foo> mapReserve;

    // --> Snapshot A

    mapReserve.reserve(1000);

    // --> Snapshot B

    for(uint32_t i = 0; i < 1000; ++i)
    {
        mapNoReserve.insert(std::make_pair(i, Foo()));
        mapReserve.insert(std::make_pair(i, Foo()));
    }

    // -> Snapshot C

    return 0;
}

在 cmets 指示的地方,我拍了一张内存快照。

结果如下:

快照 A:

┌──────────────┬──────────────┬──────────────┐
|     Map      | Size (Bytes) | Bucket Count |
|--------------|--------------|--------------|
| mapNoReserve | 64           | 8            |
| mapReserve   | 64           | 8            |
└──────────────┴──────────────┴──────────────┚

快照 B:

┌──────────────┬──────────────┬──────────────┐
|     Map      | Size (Bytes) | Bucket Count |
|--------------|--------------|--------------|
| mapNoReserve | 64           | 8            |
| mapReserve   | 8231         | 1024         |
└──────────────┴──────────────┴──────────────┚

快照 C:

┌──────────────┬──────────────┬──────────────┐
|     Map      | Size (Bytes) | Bucket Count |
|--------------|--------------|--------------|
| mapNoReserve | 24024        | 1024         |
| mapReserve   | 24024        | 1024         |
└──────────────┴──────────────┴──────────────┚

解读:

从快照中可以看出,一旦我们开始向它们添加元素,这两个地图的大小似乎都在增长,甚至是名为 reserve 的地图。

那么reserve 是否提供了一个好处,即使内存仍然被分配?我会说是的,原因有两个:(1) 它为存储桶预先分配内存,(2) 它可以防止需要 rehash,如前所述,它会完全重建地图。

【讨论】:

  • 在链接的问题中,据说rehash()reserve() 都预先分配了存储桶,但也有人说 reserve(pow(2, x)): 但现在 pow(2, x) 是您计划插入的元素数量。那是什么意思?这是否意味着reserve() 会知道如果你给它 100 万作为参数它需要 X 个桶?无论如何,如果你保留了你需要的桶,那你为什么还需要分配内存呢?保留只是一种优化,以便您以后不必创建新存储桶,但实际元素仍需要为它们分配内存?
  • 是的,当您调用 reserve(n) 时,会创建足够的存储桶以至少容纳 n 项目。如果您随后将 &gt; n 项目添加到地图,则可能会根据负载因子触发重新哈希。
  • 我仍然看不出这与分配的内存有何不同,抱歉。您能否解释一下为n 元素保留存储桶是否与为n 元素分配内存相同?因为尽管保留,但当我插入时,它显然会在内部调用new()
  • 感谢您的详细回答,它消除了一些误解,即使我最终根本没有使用地图(原因与问题无关)。
  • 别担心,谢谢你的好问题。一路走来,我对unordered_map 的内部运作有了一些了解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-05
  • 2016-05-28
  • 2012-01-17
相关资源
最近更新 更多