1. ホーム
  2. c++

[解決済み] std::unordered_mapの実装方法

2023-05-01 04:15:19

疑問点

c++ unordered_map 衝突処理、リサイズとリハッシュ

これは私が以前行った質問ですが、unordered_mapの実装方法について多くの混乱があることがわかりました。他の多くの人々が私と同じように混乱することを確信しています。標準を読まなくても、私が知っている情報に基づいています。

すべてのunordered_mapの実装は、バケットの配列に外部ノードへのリンクリストを格納します。 ノードへのリンクリストをバケットの配列に格納する... いいえ、それは最も一般的な用途のためのハッシュマップを実装する最も効率的な方法では全くありません。 それは、ほとんどの一般的な用途において、ハッシュマップを実装する最も効率的な方法ではありません。 残念ながら、unordered_mapの仕様には小さな見落とし"があります。 の仕様に小さな見落としがあり、この振る舞いを要求しているのです。要求される動作は 要素へのイテレータは、他の要素を挿入または削除する際にも有効でなければならないことです。 他の要素

私は誰かが実装を説明し、それがどのようにC++標準の定義に適合するか(パフォーマンス要件の観点から)、そしてそれが本当にハッシュマップデータ構造を実装する最も効率的な方法ではない場合、どのように改善することができるか、期待していたのですが?

どのように解決するのですか?

この規格では、事実上 std::unordered_set std::unordered_map - とその同胞であるquot;multi"を使用します。 オープン ハッシュ 別名 セパレート チェーン というのは、バケットの配列で、それぞれがリンクリストの先頭を保持していることを意味します†。 その要件は微妙です:それは結果です。

  • デフォルトの max_load_factor() は 1.0 です (つまり、テーブルが size() が1.0倍を超えると bucket_count() となります。
  • は、その負荷率を超えて成長しない限り、テーブルがリハッシュされないことを保証します。

それは、ハッシュテーブル実装の他の主要なカテゴリとの衝突があるため、チェーニングなしでは実用的ではありません - 。 クローズド ハッシュ 別名 オープンアドレッシング - が圧倒的な存在感を示すようになります。 load_factor() ]( https://en.cppreference.com/w/cpp/container/unordered_map/load_factor ) は1に近づく。

参考文献です。

<ブロッククオート

23.2.5/15:この insertemplace の場合,メンバは,イテレータの有効性に影響を与えないものとする。 (N+n) < z * B である場合,ここで N は挿入操作の前のコンテナ内の要素数である。 n は挿入された要素の数である。 B はコンテナのバケット数、そして z はコンテナの最大負荷率である。

のうち 効果 のうち、23.5.4.2/1にあるコンストラクタの max_load_factor() は以下を返します。 1.0 .

空のバケットを渡すことなく最適な反復処理を行うために、GCC の実装では、イテレータを持つバケットを、すべての値を保持する単一のシングルリンクリストに埋めます:イテレータはすぐに要素を指す の直前の要素を指します。 イテレータはそのバケツの要素の直前の要素を指すので next のポインタは、バケツの最後の値を消去する際に、再配線することができます。

引用された文章について。

いいえ、それはほとんどの一般的な用途のためのハッシュマップを実装する最も効率的な方法では全くありません。残念ながら、unordered_map の仕様における小さな見落としが、この動作を要求しているのです。要求される動作は、他の要素を挿入または削除するときに、要素へのイテレータが有効であり続けることです。

見落としはありません...行われたことは非常に意図的であり、十分な認識を持って行われました。 他の妥協点があったことは事実ですが、オープン ハッシュ/チェーン方式は一般的な使用における妥当な妥協点であり、平凡なハッシュ関数による衝突に合理的に対処でき、キー/値の型の大小にかかわらず無駄がなく、任意の数の insert / erase のペアを、多くの閉じたハッシュの実装のように、徐々にパフォーマンスを低下させることなく使用できます。

意識の証拠に Matthew Austernの提案はこちら :

<ブロッククオート

私は、汎用的なフレームワークでオープンアドレッシングを満足に実装したものを知りません。オープンアドレッシングは多くの問題を提示します。

- 空いている位置と占められた位置を区別することが必要です。

- ハッシュテーブルをデフォルトコンストラクタを持つ型に限定し、すべての配列要素を前もって構築しておくか、あるいはその要素のいくつかがオブジェクトで他のいくつかが生のメモリである配列を維持することが必要である。

- オープンアドレッシングは衝突管理を難しくします。ハッシュコードがすでに占有されている場所にマップされる要素を挿入する場合、次に試す場所を指示するポリシーが必要です。これは解決済みの問題ですが、最もよく知られている解決策は複雑です。

- 要素の消去が許可されている場合、衝突管理は特に複雑です。(議論のために Knuth を参照してください。) 標準ライブラリのコンテナ クラスは、消去を許可する必要があります。

- オープンアドレッシングのための衝突管理スキームは、最大N個の要素を保持できる固定サイズの配列を仮定する傾向があります。標準ライブラリのコンテナクラスは、利用可能なメモリの限界まで、新しい要素が挿入されたときに必要に応じて大きくすることができるはずです。

これらの問題を解決することは興味深い研究プロジェクトかもしれませんが、C++のコンテキストでの実装経験がない場合、オープンアドレスのコンテナクラスを標準化することは不適切でしょう。

具体的には、バケットに直接格納できるほど小さなデータ、未使用のバケット用の便利なセンチネル値、および優れたハッシュ関数を持つ挿入専用のテーブルの場合、閉じたハッシュアプローチはおよそ1桁速く、劇的に少ないメモリを使用するかもしれませんが、これは汎用的ではありません。

ハッシュ テーブルの設計オプションとその意味の完全な比較と詳細な説明は、ここで適切に扱うには広すぎるため、S.O. のトピックからは外れています。