[解決済み] gcc std::unordered_map の実装は遅いですか?もしそうなら、それはなぜですか?
質問
C++でパフォーマンス重視のソフトを開発しています。そこでは並列ハッシュマップが必要で、それを実装しています。そこで、私たちの並列ハッシュマップが、以下のものと比較してどれくらい遅いかを把握するためにベンチマークを書きました。
std::unordered_map
.
しかし
std::unordered_map
は信じられないほど遅いようです...。これはマイクロベンチマークです(同時実行マップでは、ロックが最適化されないように新しいスレッドを生成しています。
google::dense_hash_map
でベンチマークを行うためです。)
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秒と非常に時間がかかるようです。また、ルックアップ時間もかなり高いようです。
私の質問:これはなぜですか?私はstackoverflowで他の質問を読みました。
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
に値を挿入するのは、なぜそれほどまでに高価なのでしょうか (最初に十分なスペースを確保しても、パフォーマンスはそれほど良くなりません。)
EDIT。
まず最初に: そうです、提示されたベンチマークは完璧ではありません - これは私たちがいろいろと遊んでみた結果、単なるハックに過ぎないからです (たとえば
uint64
ディストリビューションで int を生成するのは実際には良いアイデアではなく、ループで 0 を除外するのはちょっと馬鹿げてるなど) です。
現時点では、ほとんどのコメントは、unordered_map のために十分なスペースを事前に割り当てることによって、より速くすることができると説明しています。私たちのアプリケーションでは、これは不可能です。私たちはデータベース管理システムを開発しており、トランザクション中にいくつかのデータ (たとえば、ロック情報) を格納するためにハッシュ マップを必要としています。つまり、このマップは1エントリ(ユーザーが1回挿入してコミットするだけ)から数十億エントリ(テーブルのフルスキャンが発生する場合)にもなり得ます。ここに十分なスペースを事前に割り当てることは不可能です (そして、最初に多くを割り当てると、あまりにも多くのメモリを消費してしまいます)。
さらに、私の質問を十分に明確に述べなかったことをお詫びします。私は、unordered_map を高速にすることにあまり興味がなく (googles dense hash map を使用して、私たちにとってはうまくいきます)、この大きなパフォーマンスの違いがどこから来るのか本当に理解していません。これは単なる事前割り当てではありません (十分な事前割り当てメモリがあっても、密集したマップは unordered_map よりも桁違いに高速で、私たちのハンドバックされた同時マップはサイズ 64 の配列から始まるので unordered_map よりも小さいものです)。
のパフォーマンスが悪い理由は何でしょうか?
std::unordered_map
? あるいは別の問いかけをします。の実装を書くことは可能でしょうか?
std::unordered_map
インターフェイスの実装を書くことはできますか? それとも、実装者が非効率的な方法を選んで実装するよう強制する何かが標準にあるのでしょうか?
EDIT 2:
プロファイリングしてみると、整数の割り算に多くの時間を使っていることがわかります。
std::unordered_map
は配列のサイズに素数を使用していますが、他の実装では2の累乗を使用しています。なぜ
std::unordered_map
は素数を使うのでしょうか?ハッシュが悪いものであった場合に、より良いパフォーマンスをするためでしょうか?良いハッシュの場合、それは何の違いもありません。
編集 3:
これらの数値は
std::map
:
inserts: 16462
get : 16978
なぜ、挿入は
std::map
に挿入する方が
std::unordered_map
... つまり、WAT?
std::map
は局所性が悪く(木 対 配列)、より多くの割り当てが必要で(挿入ごと 対 リハッシュごと + さらに衝突ごとに ~1)、最も重要なのは、別のアルゴリズム複雑性(O(logn) 対 O(1)) があることです!
どのように解決するのか?
gcc-4.7の問題です!
とは gcc-4.7
inserts: 37728
get : 2985
とは gcc-4.6
inserts: 2531
get : 1565
そこで
std::unordered_map
が壊れています (または、私のインストールは、Ubuntu 上の gcc-4.7.0 のインストールであり、別のインストールは debian testing 上の gcc 4.7.1 です。)。
私はバグレポートを提出します。を使用しないでください。
std::unordered_map
を gcc 4.7 で使ってはいけません!
関連
-
[解決済み】getline()が何らかの入力の後に使用されると動作しない 【重複あり
-
[解決済み】IntelliSense:オブジェクトに、メンバー関数と互換性のない型修飾子がある
-
[解決済み] using namespace std;」はなぜバッドプラクティスだと言われるのですか?
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] なぜC++はPythonよりもstdinからの行の読み込みが遅いのですか?
-
[解決済み] なぜテンプレートはヘッダーファイルでしか実装できないのですか?
-
[解決済み] なぜ、オブジェクトそのものではなく、ポインタを使用しなければならないのですか?
-
[解決済み] 0.1fを0にすると、なぜ10倍もパフォーマンスが落ちるのですか?
-
[解決済み] std::move()とは何ですか?また、どのような場合に使用するのですか?
-
[解決済み] const std::string & をパラメータとして渡す時代は終わったのでしょうか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】コンストラクターでのエラー:識別子を期待されますか?
-
[解決済み】C++ 非推奨の文字列定数から「char*」への変換について
-
[解決済み】Visual Studio 2015で「非標準の構文; '&'を使用してメンバーへのポインターを作成します」エラー
-
[解決済み】Visual Studio 2015で「非標準の構文。'&'を使用してメンバーへのポインターを作成します」エラー
-
[解決済み】C++のGetlineの問題(オーバーロードされた関数 "getline "のインスタンスがない
-
[解決済み] string does not name a type Errorが発生するのはなぜですか?
-
[解決済み】抽象クラス型の無効なnew-expression
-
[解決済み】浮動小数点例外エラーが発生する: 8
-
[解決済み】c++でstd::vectorを返すための効率的な方法
-
[解決済み】1つ以上の多重定義されたシンボルが見つかる