私たちは、C ++で高性能の重要なソフトウェアを開発しています。そこで、並行ハッシュマップが必要で、それを実装しました。そこで、並行ハッシュマップがと比較してどれほど遅いかを把握するためのベンチマークを作成しましたstd::unordered_map
。
しかし、std::unordered_map
信じられないほど遅いようです...だから、これは私たちのマイクロベンチマークです(並行マップでは、ロックが最適化されないことを確認するために新しいスレッドを生成し、私も0を挿入しないことに注意してくださいgoogle::dense_hash_map
。 null値が必要です):
boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> dist(std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max());
std::vector<uint64_t> vec(SIZE);
for (int i = 0; i < SIZE; ++i) {
uint64_t val = 0;
while (val == 0) {
val = dist(rng);
}
vec[i] = val;
}
std::unordered_map<int, long double> map;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < SIZE; ++i) {
map[vec[i]] = 0.0;
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "inserts: " << elapsed.count() << std::endl;
std::random_shuffle(vec.begin(), vec.end());
begin = std::chrono::high_resolution_clock::now();
long double val;
for (int i = 0; i < SIZE; ++i) {
val = map[vec[i]];
}
end = std::chrono::high_resolution_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "get: " << elapsed.count() << std::endl;
(編集:ソースコード全体はここにあります:http : //pastebin.com/vPqf7eya)
の結果std::unordered_map
は次のとおりです。
inserts: 35126
get : 2959
の場合google::dense_map
:
inserts: 3653
get : 816
私たちの手で裏打ちされた並行マップ(これはロックを行いますが、ベンチマークはシングルスレッドですが、別のスポーンスレッドです):
inserts: 5213
get : 2594
pthreadをサポートせずにベンチマークプログラムをコンパイルし、すべてをメインスレッドで実行すると、手動の同時実行マップに対して次の結果が得られます。
inserts: 4441
get : 1180
次のコマンドでコンパイルします。
g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc
したがって、特に挿入はstd::unordered_map
非常に高価であるように見えます-他のマップでは3秒から5秒ですが、35秒です。また、ルックアップ時間はかなり長いようです。
私の質問:これはなぜですか?私は誰かが尋ねるスタックオーバーフローに関する別の質問を読みました、なぜstd::tr1::unordered_map
彼自身の実装より遅いのですか?最高評価の回答は、std::tr1::unordered_map
より複雑なインターフェースを実装する必要があると述べています。しかし、この引数はわかりません。concurrent_mapでバケットアプローチを使用し、バケットアプローチも使用しますstd::unordered_map
(google::dense_hash_map
そうではありませんstd::unordered_map
が、少なくとも、手動でバックアップした同時実行セーフバージョンよりも高速である必要がありますか?)。それとは別に、ハッシュマップのパフォーマンスを低下させる機能を強制するインターフェイスには何も表示されません...
だから私の質問:std::unordered_map
非常に遅いように見えるのは本当ですか?いいえの場合:何が問題ですか?はいの場合:その理由は何ですか。
そして私の主な質問:なぜ値がstd::unordered_map
非常に高価なものに挿入されているのですか(最初に十分なスペースを予約したとしても、パフォーマンスはそれほど良くありません-再ハッシュは問題ではないようです)?
編集:
まず第一に、はい、提示されたベンチマークは完璧ではありません-これは私たちが多くのことを試し、ハックにすぎないためです(たとえば、uint64
intを生成するディストリビューションは実際には良いアイデアではなく、ループ内で0を除外します)一種の愚かさなどです...)。
現在ほとんどのコメントで、unordered_mapに十分なスペースを事前に割り当てることでunordered_mapを高速化できると説明しています。私たちのアプリケーションでは、これはまったく不可能です。データベース管理システムを開発していて、トランザクション中にデータ(たとえば、情報のロック)を格納するためにハッシュマップが必要です。したがって、このマップは1(ユーザーが1回の挿入とコミットを行うだけ)から数十億のエントリ(フルテーブルスキャンが発生した場合)までのすべてに対応できます。ここで十分なスペースを事前に割り当てることは不可能です(そして、最初に多くを割り当てるだけでは、多くのメモリを消費します)。
さらに、私は私の質問を十分に明確に述べていなかったことをお詫び申し上げます:私はunordered_mapを高速にすることに本当に興味がありません(Googleの高密度ハッシュマップを使用するとうまくいきます)、この大きなパフォーマンスの違いがどこから来るのか本当に理解していません。これは単なる事前割り当てではありません(十分に事前割り当てされたメモリがあっても、密なマップはunordered_mapよりも桁違いに高速です。手動でバックアップされた並行マップはサイズ64の配列で始まるため、unordered_mapよりも小さいです)。
それで、この悪いパフォーマンスの理由は何std::unordered_map
ですか?または別の質問:std::unordered_map
標準に準拠し、(ほぼ)グーグルの密なハッシュマップと同じ速さのインターフェイスの実装を記述できますか?または、実装者がそれを実装する非効率的な方法を選択することを強制する標準に何かありますか?
編集2:
プロファイリングにより、整数の除算に多くの時間が費やされていることがわかります。std::unordered_map
配列サイズには素数を使用しますが、他の実装では2の累乗を使用します。なぜstd::unordered_map
素数を使用するのですか?ハッシュが悪い場合にパフォーマンスを向上させるには?良いハッシュの場合、違いはありません。
編集3:
これらは、次の番号ですstd::map
。
inserts: 16462
get : 16978
Sooooooo:への挿入std::map
よりも挿入への方が速いのはなぜstd::unordered_map
ですか...つまり、WATですか?std::map
局所性が低く(ツリーと配列)、より多くの割り当てを行う必要があります(挿入ごとvsリハッシュごと+ +衝突ごとに〜1)。最も重要なのは、別のアルゴリズムの複雑さ(O(logn)vs O(1))です!
SIZE
。