C++でのunordered_map
に関する最近の話で、私は以前はmap
を使っていたほとんどの場合、unordered_map
を使うべきだと気付きました。検索の効率のため、(償却O(1)vs.O(log n))ほとんどの場合、私はマップとしてint
またはstd::strings
をキーとして使用します。したがって、ハッシュ関数の定義に問題はありません。 std::map
より単純な型の場合にunordered_map
を使用する理由を見つけることができないということに気づくほど、私は気付くようになりました - 私はインターフェースを調べました、そして重要なことは何も見つけませんでした私のコードに影響を与えるだろう違い。
それ故、質問です - int
やstd::map
のような単純型の場合にunordered map
よりstd::string
を使う本当の理由は何ですか?
私は厳密にプログラミングの観点から質問しています - 私はそれが完全に標準と見なされていないこと、そしてそれが移植の問題を引き起こすかもしれないことを知っています。
また、正しい答えの1つは、「データセットが小さいほど効率的」になると予想されます(オーバーヘッドが小さいためです)。 - それ故、私は質問をキーの数が自明でない(> 1 024)場合に限定したい。
編集:ああ、当たり前のこと忘れてました(ありがとうGMan!) - はい、地図はもちろん注文されています - 他にも理由があるので探しています
map
を忘れずに、要素を順番に並べてください。あなたがそれをあきらめることができないなら、明らかにあなたはunordered_map
を使うことができません。
他に念頭に置いておくべきことはunordered_map
は一般により多くのメモリを使用するということです。 map
は、いくつかのハウスキーピングポインタを持ち、それから各オブジェクトのメモリを持ちます。反対に、unordered_map
は大きな配列(いくつかの実装ではかなり大きくなる可能性があります)を持ち、それから各オブジェクトに追加のメモリを持ちます。あなたがメモリを意識する必要があるなら、map
は大きな配列を欠いているので、より良く証明されるべきです。
それで、もしあなたが純粋な検索 - 検索を必要とするなら、私はunordered_map
が行くべき道であると言うでしょう。しかし、常にトレードオフがあり、それらを買う余裕がない場合は使用できません。
個人的な経験から、メインエンティティのルックアップテーブルでmap
の代わりにunordered_map
を使用すると、パフォーマンスが大幅に向上することがわかりました(もちろん測定されます)。
一方で、要素の挿入と削除を繰り返すのははるかに遅いことがわかりました。比較的静的な要素の集まりには最適ですが、大量の挿入や削除を行っている場合は、ハッシュ+バケット化が足りないようです。 (注:これは何度も繰り返しました。)
あなたのstd::map
とstd::unordered_map
の実装のスピードを比較したいのなら、time_hash_mapプログラムで時間を計るグーグルの sparsehash プロジェクトを使うことができます。たとえば、x86_64 Linuxシステム上のgcc 4.4.2の場合
$ ./time_hash_map
TR1 UNORDERED_MAP (4 byte objects, 10000000 iterations):
map_grow 126.1 ns (27427396 hashes, 40000000 copies) 290.9 MB
map_predict/grow 67.4 ns (10000000 hashes, 40000000 copies) 232.8 MB
map_replace 22.3 ns (37427396 hashes, 40000000 copies)
map_fetch 16.3 ns (37427396 hashes, 40000000 copies)
map_fetch_empty 9.8 ns (10000000 hashes, 0 copies)
map_remove 49.1 ns (37427396 hashes, 40000000 copies)
map_toggle 86.1 ns (20000000 hashes, 40000000 copies)
STANDARD MAP (4 byte objects, 10000000 iterations):
map_grow 225.3 ns ( 0 hashes, 20000000 copies) 462.4 MB
map_predict/grow 225.1 ns ( 0 hashes, 20000000 copies) 462.6 MB
map_replace 151.2 ns ( 0 hashes, 20000000 copies)
map_fetch 156.0 ns ( 0 hashes, 20000000 copies)
map_fetch_empty 1.4 ns ( 0 hashes, 0 copies)
map_remove 141.0 ns ( 0 hashes, 20000000 copies)
map_toggle 67.3 ns ( 0 hashes, 20000000 copies)
私は、GManが述べたのとほぼ同じ要点を述べています。使用の種類によっては、std::map
はstd::tr1::unordered_map
よりも速くなることがあります(VS 2008 SP1に含まれる実装を使用)。
覚えておくべきいくつかの複雑な要因があります。たとえば、std::map
では、キーを比較しています。つまり、ツリーの左右のサブブランチを区別するのに必要なのは、キーの最初の部分だけです。私の経験では、キー全体を見たのは、intのようなものを使用している場合が1つの命令で比較できる場合がほとんどです。 std :: stringのようなより一般的なキータイプでは、数文字程度しか比較しないことがよくあります。
対照的に、まともなハッシュ関数は、常にentireキーを調べます。つまり、テーブルルックアップが一定の複雑さであっても、ハッシュ自体はほぼ線形の複雑さを持ちます(ただし、項目の数ではなくキーの長さに依存します)。キーとして長い文字列を使うと、std::map
がstartその検索の前にunordered_map
が検索を終了するかもしれません。
第二に、ハッシュテーブルのサイズを変更する方法はいくつかありますが、それらのほとんどはかなり遅いです - ルックアップが挿入や削除よりもかなりより頻繁でない限り、std :: mapはしばしばstd::unordered_map
より速いです。
もちろん、前の質問に対するコメントで述べたように、木の表を使うこともできます。これには長所と短所の両方があります。一方では、最悪の場合を木の場合に限定します。 (少なくともそれをしたときには)固定サイズのテーブルを使用したので、高速な挿入と削除も可能です。allテーブルのサイズ変更を排除すると、ハッシュテーブルをずっとシンプルにして、通常は速くすることができます。
もう1つのポイントは、ハッシュとツリーベースのマップの要件が異なることです。ハッシュは明らかにハッシュ関数と等号比較を必要とします。そこでは順序付きマップはより小さい比較を必要とします。もちろん、私が述べたハイブリッドは両方を必要とします。もちろん、文字列をキーとして使用する一般的なケースでは、これは実際には問題になりませんが、一部のタイプのキーはハッシュよりも順序付けに適しています(またはその逆)。
私は@Jerry Coffinからの回答に興味をそそられました、それは順序付けられた地図が長い文字列上でパフォーマンスの向上を示すことを示唆していました(これは Pastebin からダウンロードできます)。ランダムな文字列のコレクションにのみ当てはまるようですが、マップがソートされた辞書(かなりの量のプレフィックス重複を含む単語を含む)で初期化されると、おそらく値を取得するのに必要なツリーの深さが増えるため、この規則は破綻します。結果を以下に示します。1番目の数字列は挿入時間、2番目は取り出し時間です。
g++ -g -O3 --std=c++0x -c -o stdtests.o stdtests.cpp
g++ -o stdtests stdtests.o
gmurphy@interloper:HashTests$ ./stdtests
# 1st number column is insert time, 2nd is fetch time
** Integer Keys **
unordered: 137 15
ordered: 168 81
** Random String Keys **
unordered: 55 50
ordered: 33 31
** Real Words Keys **
unordered: 278 76
ordered: 516 298
ただ指摘しておきますが…unordered_map
sにはたくさんの種類があります。
ハッシュマップで Wikipedia Article を調べてください。どの実装が使用されたかに依存して、検索、挿入および削除の観点からの特性はかなり大きく異なるかもしれません。
そして、それがunordered_map
をSTLに追加することで私が最も心配することです。彼らは私たちがPolicy
の道を進むことを疑うので、彼らは特定の実装を選ばなければならないでしょう。平均的な使用で他の場合には何もありません...
たとえば、ハッシュマップの中には線形ハッシュを持っているものがあり、ハッシュマップ全体を一度にハッシュし直すのではなく、挿入ごとに一部分をハッシュし直すことがあり、コストの削減に役立ちます。
他の例:ハッシュマップの中には単純なノードのリストをバケツに使うものもあれば、マップを使うものもあればノードを使わないで最も近いスロットを見つけるものもあります。前面にあります(キャッシュのようなものです)。
そのため、現時点ではstd::map
またはおそらくloki::AssocVector
(凍結データセット用)を好む傾向があります。
誤解しないでください、私はstd::unordered_map
を使いたいです、そして将来私はそうするかもしれません。この結果。
map
はすべての要素へのイテレータを安定に保ちます。C++ 17では、イテレータを無効にすることなく(そして潜在的な割り当てなしで正しく実装されていれば)要素をあるmap
から他の要素に移動できます。map
のタイミングは、大きな割り当てを必要としないため、一般的により一貫性があります。unordered_map
を使用するstd::hash
は信頼できない入力で供給されるとDoSに対して脆弱です(MurmurHash2を定数シードで使用します - シードが実際に役立つとは思わないので、 https://emboss.github.io/] blog/2012/12/14/breaking-murmur-hash-flooding-dos-reloaded / )。ハッシュテーブルは一般的なマップ実装よりも高い定数を持っています。これは小さなコンテナにとって重要になります。最大サイズは10、100、あるいは1,000以上でしょうか。定数はこれまでと同じですが、O(log n)はO(k)に近くなります。 (対数の複雑さはまだ本当に良いことを覚えていてください。)
良いハッシュ関数を作るものはあなたのデータの特性に依存します。したがって、カスタムハッシュ関数を検討する予定がない場合(ただし、後で変更する可能性があります。ほとんどすべてのデータソースに対してデフォルトで適切に機能するように選択されています)。その場合は、ハッシュテーブルではなく、デフォルトでmapを使用するように、最初はmapの性質が十分に役立ちます。
そのようにして、あなたは他の(通常UDT)型のためにハッシュ関数を書くことさえ考える必要はなく、単にop <(あなたはとにかく欲しい)を書くだけです。
他の答えには理由があります。これはもう一つです。
std :: map(平衡二分木)操作は、O(log n)と最悪の場合O(log n)で償却されます。 std :: unordered_map(hash table)操作はO(1)および最悪の場合O(n)に償却されます。
これが実際にどのように実行されるかは、ハッシュテーブルがO(n)操作で時々「しゃっくり」することです。それが許容できない場合は、std :: unordered_mapよりもstd :: mapをお勧めします。
私は最近50000マージ&ソートを行うテストをしました。つまり、文字列キーが同じ場合は、バイト文字列をマージします。そして最終的な出力はソートされるべきです。したがって、これにはすべての挿入の検索が含まれます。
map
name__の実装では、ジョブを完了するのに200ミリ秒かかります。 unordered_map
+ map
name__の場合、unordered_map
の挿入には70ミリ秒、map
name__の挿入には80ミリ秒かかります。そのため、ハイブリッド実装は50ミリ秒速くなります。
map
name__を使う前に、二度考えなければなりません。プログラムの最終結果でデータをソートするだけでよい場合は、ハイブリッドソリューションの方が良い場合があります。
まとめ
順序付けは重要ではないと仮定します。
std::unordered_map
を使用します。std::map
を使用してください。これは、読み取りがO(log n)
であるためです。std::map
は良いオプションです。std::unordered_map
を使用してください歴史的背景
ほとんどの言語では、順不同マップ(別名ハッシュベースの辞書)がデフォルトマップですが、C++ではデフォルトマップとして順序マップが使用されます。どうしてこうなりました?一部の人々は、C++委員会が独自の知恵でこの決定を下したと誤って想定していますが、残念ながらそれより真実は曖昧です。
それがどのように実装されることができるかに関してあまりにも多くのパラメータがないので、C++がデフォルトとして順序付きマップで終わったのは広く 信じられている です。一方、ハッシュベースの実装にはたくさんのことがあります。そのため、標準化の際にグリッドロックを避けるために、順序付けされたマップを使用して、これらを たどり着いた します。 2005年頃には、多くの言語がすでにハッシュベースの実装の優れた実装を持っていたので、委員会が新しいstd::unordered_map
を受け入れるほうが簡単でした。完璧な世界では、std::map
は順不同で、別の型としてstd::ordered_map
があります。
パフォーマンス
以下の2つのグラフは、それ自体が言えるはずです( source )。
上記のすべてに少し追加:
要素はソートされているので、範囲ごとに要素を取得する必要がある場合はmap
を使用してください。境界を越えて要素を繰り返すことができます。