web-dev-qa-db-ja.com

std :: unordered_mapの実装方法

c ++ unordered_mapコリジョン処理、サイズ変更、リハッシュ

これは私が開いた以前の質問であり、unordered_mapがどのように実装されているかについて多くの混乱があることを見てきました。他の多くの人が私とその混乱を共有していると確信しています。標準を読まずに知っている情報に基づいて:

すべてのunordered_map実装は、リンクされたリストをバケットの配列内の外部ノードに保存します...いいえ、それは最も一般的な用途向けにハッシュマップを実装する最も効率的な方法ではありません。残念ながら、unordered_mapの仕様に小さな「見落とし」がある場合、この動作が必要になります。必要な動作は、他の要素を挿入または削除するときに要素の反復子が有効のままでなければならないことです。

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

46
ralzaul

標準は、オープンハッシュを使用する_std::unordered_set_および_std::unordered_map_実装を効果的に義務付けています。これは、それぞれが論理(および実際の)リストの先頭を保持するバケットの配列を意味します。その要件は微妙です:デフォルトの最大負荷係数が1.0であり、その負荷係数を超えない限りテーブルが再ハッシュされないという保証の結果です:閉じたハッシュとの衝突が圧倒的になるため、チェーンなしでは非実用的です負荷率が1に近づく:

23.2.5/15:_(N+n) < z * B_の場合、insertおよびemplaceメンバはイテレータの有効性に影響を与えません。ここで、Nはコンテナ内の要素の数です挿入操作の前のnは挿入された要素の数、Bはコンテナのバケット数、zはコンテナの最大負荷係数です。

23.5.4.2/1のコンストラクターのEffectsの中で:max_load_factor()は_1.0_を返します。

(空のバケットを渡さずに最適な反復を可能にするために、GCCの実装は、すべての値を保持する単一の単一リンクリストにバケットを反復子で満たします。反復子は、そのバケットの要素の直前の要素を指し、バケットの最後の値を消去する場合は再配線されます。)

引用するテキストについて:

いいえ、ほとんどの一般的な用途にハッシュマップを実装する最も効率的な方法ではありません。残念ながら、unordered_mapの仕様の小さな「見落とし」には、この動作がほとんど必要です。必要な動作は、他の要素を挿入または削除するときに要素の反復子が有効のままでなければならないことです。

「監視」はありません...行われたことは非常に意図的であり、完全な認識を持って行われました。他の妥協案が打たれた可能性もありますが、オープンハッシュ/チェーンアプローチは一般的な使用にとって合理的な妥協策であり、平凡なハッシュ関数からの衝突に適度にエレガントに対処し、小規模または大規模なキー/値型では無駄になりません。そして、多くのクローズドハッシュの実装のようにパフォーマンスを徐々に低下させることなく、任意の数のinsert/eraseペアを処理します。

認識の証拠として、 ここでのマシューオースターンの提案

一般的なフレームワークでのオープンアドレッシングの満足のいく実装については知りません。オープンアドレッシングには多くの問題があります。

•空いている位置と占有されている位置を区別する必要があります。

•ハッシュテーブルを既定のコンストラクターを使用した型に制限し、すべての配列要素を事前に構築するか、要素の一部がオブジェクトで、その他が生メモリである配列を維持する必要があります。

•オープンアドレス指定により、衝突管理が困難になります。ハッシュコードが既に使用されている場所にマップされる要素を挿入する場合、次に試す場所を指示するポリシーが必要です。これは解決された問題ですが、最もよく知られている解決策は複雑です。

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

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

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

特に、バケットに直接格納するのに十分なデータ、未使用のバケットの便利なセンチネル値、および適切なハッシュ関数を使用した挿入専用テーブルの場合、クローズドハッシュアプ​​ローチはおおよそ1桁速く、メモリの使用量が大幅に少なくなりますが、それは一般的な目的ではありません。

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

60
Tony Delroy