Kami sedang mengembangkan perangkat lunak kritis berkinerja tinggi dalam C ++. Di sana kita membutuhkan peta hash bersamaan dan menerapkannya. Jadi kami menulis patokan untuk mencari tahu, seberapa lambat peta hash serentak kami dibandingkan std::unordered_map
.
Tapi, std::unordered_map
sepertinya sangat lambat ... Jadi ini adalah tolok ukur mikro kami (untuk peta bersamaan kami menghasilkan utas baru untuk memastikan bahwa penguncian tidak dioptimalkan dan perhatikan bahwa saya tidak pernah memasukkan 0 karena saya juga melakukan tolok ukur dengan google::dense_hash_map
, yang membutuhkan nilai nol):
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;
(EDIT: seluruh kode sumber dapat ditemukan di sini: http://pastebin.com/vPqf7eya )
Hasil untuk std::unordered_map
adalah:
inserts: 35126
get : 2959
Untuk google::dense_map
:
inserts: 3653
get : 816
Untuk peta serentak yang didukung tangan kami (yang mengunci, meskipun tolok ukurnya adalah utas tunggal - tetapi dalam utas pemijahan terpisah):
inserts: 5213
get : 2594
Jika saya mengkompilasi program benchmark tanpa dukungan pthread dan menjalankan semuanya di utas utama, saya mendapatkan hasil berikut untuk peta konkuren yang didukung tangan kami:
inserts: 4441
get : 1180
Saya mengkompilasi dengan perintah berikut:
g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc
Jadi khususnya sisipan std::unordered_map
tampaknya sangat mahal - 35 detik vs 3-5 detik untuk peta lain. Juga waktu pencarian tampaknya cukup tinggi.
Pertanyaan saya: mengapa demikian? Saya membaca pertanyaan lain tentang stackoverflow di mana seseorang bertanya, mengapa std::tr1::unordered_map
lebih lambat dari implementasinya sendiri. Di sana nilai jawaban tertinggi menyatakan, bahwa std::tr1::unordered_map
kebutuhan untuk mengimplementasikan antarmuka yang lebih rumit. Tapi saya tidak bisa melihat argumen ini: kami menggunakan pendekatan ember di concurrent_map kami, std::unordered_map
menggunakan pendekatan ember juga ( google::dense_hash_map
tidak, tetapi std::unordered_map
setidaknya harus secepatnya daripada versi aman konkurensi yang didukung tangan?). Selain itu, saya tidak dapat melihat apa pun di antarmuka yang memaksa fitur yang membuat peta hash berkinerja buruk ...
Jadi pertanyaan saya: apakah benar itu std::unordered_map
terlihat sangat lambat? Jika tidak: apa yang salah? Jika ya: apa alasannya.
Dan pertanyaan utama saya: mengapa memasukkan nilai menjadi std::unordered_map
sangat mahal (bahkan jika kita memesan cukup ruang di awal, itu tidak bekerja jauh lebih baik - jadi mengulangi tampaknya tidak menjadi masalah)?
EDIT:
Pertama-tama: ya, tolok ukur yang disajikan tidak sempurna - ini karena kami sering bermain-main dengannya dan itu hanya peretasan (misalnya uint64
distribusi untuk menghasilkan int pada praktiknya bukan ide yang baik, kecualikan 0 dalam satu lingkaran agak bodoh dll ...).
Saat ini sebagian besar komentar menjelaskan, bahwa saya dapat membuat unordered_map lebih cepat dengan mengalokasikan ruang yang cukup untuk itu. Dalam aplikasi kami ini tidak mungkin: kami sedang mengembangkan sistem manajemen database dan membutuhkan peta hash untuk menyimpan beberapa data selama transaksi (misalnya mengunci informasi). Jadi peta ini dapat terdiri dari 1 (pengguna hanya membuat satu penyisipan dan melakukan) hingga miliaran entri (jika pemindaian tabel lengkap terjadi). Tidak mungkin mengalokasikan cukup ruang di sini (dan mengalokasikan banyak di awal akan menghabiskan terlalu banyak memori).
Selain itu, saya minta maaf, karena saya tidak menyatakan pertanyaan saya dengan cukup jelas: Saya tidak terlalu tertarik untuk membuat unordered_map dengan cepat (menggunakan peta hash padat googles bekerja dengan baik untuk kami), saya hanya tidak begitu mengerti dari mana perbedaan kinerja yang besar ini berasal . Ini tidak bisa hanya pra-alokasi (bahkan dengan memori yang cukup dialokasikan, peta padat adalah urutan besarnya lebih cepat daripada unordered_map, peta konkuren yang didukung tangan dimulai dengan larik berukuran 64 - jadi lebih kecil dari unordered_map).
Jadi apa alasan dari kinerja buruk ini std::unordered_map
? Atau ditanyakan secara berbeda: Bisakah seseorang menulis implementasi std::unordered_map
antarmuka yang sesuai standar dan (hampir) secepat peta hash padat Google? Atau adakah sesuatu dalam standar yang memaksa pelaksana untuk memilih cara yang tidak efisien untuk mengimplementasikannya?
EDIT 2:
Dengan membuat profil saya melihat bahwa banyak waktu digunakan untuk divions integer. std::unordered_map
menggunakan bilangan prima untuk ukuran larik, sedangkan implementasi lainnya menggunakan pangkat dua. Mengapa std::unordered_map
menggunakan bilangan prima? Untuk tampil lebih baik jika hashnya buruk? Untuk hash yang bagus, tidak ada bedanya.
EDIT 3:
Ini adalah angka-angka untuk std::map
:
inserts: 16462
get : 16978
Sooooooo: mengapa menyisipkan menjadi std::map
lebih cepat daripada menyisipkan ke std::unordered_map
... maksud saya WAT? std::map
memiliki lokalitas yang lebih buruk (pohon vs larik), perlu membuat lebih banyak alokasi (per penyisipan vs per rehash + plus ~ 1 untuk setiap tabrakan) dan, yang paling penting: memiliki kompleksitas algoritme lain (O (logn) vs O (1))!
SIZE
.