web-dev-qa-db-ja.com

std :: unordered_mapに予約メソッドがあるのはなぜですか?

this によると、_std::map_のスペースを予約することはできません:

いいえ、マップのメンバーは内部的にツリー構造で保存されています。保存するキーと値がわかるまで、ツリーを構築する方法はありません。

これから、_std::map_にreserve()メソッドがない理由は明らかです。これは、cppreference.comで実行されます。ただし、_std::unordered_map_doesにはreserve()メソッドがありますが、_operator[]_で使用しようとすると、insert()またはemplace()最初にreserve()を呼び出したにもかかわらず、それらはすべてメモリの割り当てに移動します。

これどうしたの?必要なスペースをreserve()が適切に予約しないのはなぜですか?また、事前にメモリを割り当てることができないマップの場合、なぜ_std::unordered_map_にreserve()メソッドがあるのですか?

16
Mikubyte

_unordered_map_コンテナにはreserveメソッドがあります。これは、mapのようなツリーではなく、バケットを使用して実装されているためです。

バケットは:

キーのハッシュ値に基づいて要素が割り当てられる、コンテナの内部ハッシュテーブル内のスロット。バケットの番号は0から(bucket_count-1)までです。 ( ソース

1つのバケットは、可変数のアイテムを保持します。この数は _load_factor_ に基づいています。 _load_factor_が特定のしきい値に達すると、コンテナはバケットの数を増やし、マップを再ハッシュします。

reserve(n) を呼び出すと、コンテナは少なくともnアイテムを保持するのに十分なバケットを作成します。

これは、バケット数をnに直接設定し、ハッシュテーブル全体の再構築をトリガーする rehash(n) とは対照的です。

参照: C++ unordered_mapでのバケットの事前割り当て

コメントに応じて編集

コメントで提起された質問に対する正確な答えがわからないため、また、私の予備調査が実を結ばなかったため、実験的にテストすることにしました。

参考までに、質問は次のようになります。

N要素のバケットの予約がn要素のメモリの割り当てと同じかどうか説明していただけませんか?

この答え によると、_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にはメリットがありますか?私は2つの理由で「はい」と言います。(1)バケットのメモリを事前に割り当てること、および(2)rehashの必要性を防ぐことができます。

23
ssell