为什么std :: unordered_map有一个保留方法?

时间:2017-02-21 16:47:55

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

根据this,您无法为std::map预留空间:

  

不,地图成员内部存储在树形结构中。   在知道键和值之前,无法构建树   那些要存储的东西。

从这一点可以明显看出std::map缺少reserve()方法的原因,它在cppreference.com上有效。但是,std::unordered_map 确实reserve()方法,但当我尝试将其与operator[]insert()emplace()一起使用时尽管我先调用了reserve(),但所有人都去分配内存。

这是怎么回事?为什么reserve()不能正确保留所需的空间?如果它与地图一样,你不能事先分配内存,那么为什么std::unordered_map甚至首先使用reserve()方法呢?

1 个答案:

答案 0 :(得分:21)

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

一个桶是:

  

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

单个存储桶包含可变数量的项目。此数字基于load_factor。当load_factor达到某个阈值时,容器会增加存储桶的数量并重新生成地图。

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

这与rehash(n)形成对比,Pre-allocating buckets in a C++ unordered_map直接将桶数设置为n并触发重建整个哈希表。

另请参阅:this answer

编辑以回应评论

由于我不知道评论中提出的问题的确切答案,并且由于我的初步研究证明并不富有成效,所以我决定通过实验进行测试。

作为参考,问题归结为:

  

请问你能解释为n个元素保留存储桶是否与为n个元素分配存储器相同?

根据enter image description here,准确检索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;
}

评论表明,我拍了一张内存快照。

结果如下:

快照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,如前面所讨论的那样完全重建地图。