C ++ ile yüksek performanslı kritik bir yazılım geliştiriyoruz. Orada eşzamanlı bir hash haritasına ihtiyacımız var ve bir tane uyguluyoruz. Bu nedenle, eşzamanlı hash haritamızın ne kadar yavaş olduğunu anlamak için bir kıyaslama yazdık std::unordered_map
.
Ancak, std::unordered_map
inanılmaz derecede yavaş görünüyor ... Yani bu bizim mikro ölçütümüz (eşzamanlı harita için kilitlemenin optimize edilmediğinden emin olmak için yeni bir iş parçacığı oluşturduk ve asla 0 eklemediğimi çünkü aynı zamanda kıyaslama yapıyorum google::dense_hash_map
, boş bir değere ihtiyaç duyan):
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;
(DÜZENLEME: kaynak kodunun tamamı burada bulunabilir: http://pastebin.com/vPqf7eya )
Sonuç std::unordered_map
şudur:
inserts: 35126
get : 2959
Şunun için google::dense_map
:
inserts: 3653
get : 816
El destekli eşzamanlı haritamız için (ölçüt tek iş parçacıklı olmasına rağmen kilitleme yapar - ancak ayrı bir spawn dizisinde):
inserts: 5213
get : 2594
Kıyaslama programını pthread desteği olmadan derler ve her şeyi ana iş parçacığında çalıştırırsam, el destekli eşzamanlı haritamız için aşağıdaki sonuçları alırım:
inserts: 4441
get : 1180
Aşağıdaki komutla derliyorum:
g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc
Bu nedenle, özellikle eklemeler std::unordered_map
son derece pahalı görünüyor - diğer haritalar için 35 saniyeye karşılık 3-5 saniye. Ayrıca arama süresi oldukça yüksek görünüyor.
Sorum: bu neden? Stackoverflow hakkında birisinin sorduğu başka bir soruyu okudum, neden std::tr1::unordered_map
kendi uygulamasından daha yavaş? En yüksek puan alan yanıtlar, std::tr1::unordered_map
daha karmaşık bir arayüzün uygulanması gerektiğini belirtir . Ancak bu argümanı göremiyorum: concurrent_map'imizde bir paket yaklaşımı std::unordered_map
kullanıyoruz, bir paket yaklaşımı da kullanıyoruz ( google::dense_hash_map
değil, ama std::unordered_map
en azından el destekli eşzamanlılık güvenli sürümümüz kadar hızlı olmalı mı?). Bunun dışında arayüzde hash haritasının kötü performans göstermesine neden olan bir özelliği zorlayan hiçbir şey göremiyorum ...
Öyleyse sorum: std::unordered_map
çok yavaş göründüğü doğru mu? Hayır ise: sorun nedir? Cevabınız evet ise: bunun nedeni nedir?
Ve asıl sorum: neden std::unordered_map
bu kadar pahalıya bir değer eklemek bu kadar pahalı (başlangıçta yeterince yer ayırsak bile, çok daha iyi performans göstermiyor - bu yüzden yeniden düzenleme sorun değil gibi görünüyor)?
DÜZENLE:
Her şeyden önce: evet, sunulan kıyaslama kusursuz değil - bunun nedeni, onunla çok fazla oynadığımız ve sadece bir hack olduğudur (örneğin, uint64
int üretecek dağıtım pratikte iyi bir fikir olmayacaktır, bir döngüde 0'ı hariç tutun aptalca vb ...).
Şu anda çoğu yorum, unordered_map'i bunun için yeterli alanı önceden ayırarak daha hızlı hale getirebileceğimi açıklıyor. Uygulamamızda bu mümkün değildir: bir veritabanı yönetim sistemi geliştiriyoruz ve bir işlem sırasında bazı verileri depolamak için bir hash haritasına ihtiyacımız var (örneğin, bilgilerin kilitlenmesi). Dolayısıyla bu harita 1'den (kullanıcı sadece bir ekleme ve taahhütte bulunur) milyarlarca girişe (tam tablo taramaları gerçekleşirse) kadar her şey olabilir. Burada yeterince alan ayırmak imkansızdır (ve sadece başlangıçta çok fazla alan ayırmak çok fazla bellek tüketecektir).
Dahası, sorumu yeterince açık bir şekilde belirtmediğim için özür dilerim: unordered_map'i hızlı yapmakla gerçekten ilgilenmiyorum (googles yoğun hash haritasının kullanılması bizim için iyi çalışıyor), bu büyük performans farklılıklarının nereden geldiğini gerçekten anlamıyorum . Bu sadece önceden tahsis olamaz (yeterince önceden tahsis edilmiş bellek olsa bile, yoğun harita, sıralı olmayan haritadan daha hızlı bir büyüklük sırasıdır, el destekli eşzamanlı haritamız 64 boyutunda bir diziyle başlar - yani unordered_map'ten daha küçüktür).
Peki bu kötü performansın sebebi std::unordered_map
nedir? Veya farklı bir şekilde sorulursa: std::unordered_map
Arayüzün standart uyumlu ve (neredeyse) googles yoğun hash haritası kadar hızlı bir uygulaması yazılabilir mi? Veya standartta, uygulayıcıyı onu uygulamak için verimsiz bir yol seçmeye zorlayan bir şey var mı?
DÜZENLEME 2:
Profil oluşturarak tam sayı bölmeleri için çok zaman kullanıldığını görüyorum. std::unordered_map
dizi boyutu için asal sayıları kullanırken, diğer uygulamalar ikinin gücünü kullanır. Neden std::unordered_map
asal sayılar kullanılır? Hash kötü ise daha iyi performans için mi? İyi karmalar için hiçbir fark yaratmaz.
DÜZENLEME 3:
Bunlar için numaralar std::map
:
inserts: 16462
get : 16978
Sooooooo: neden bir içine eklemeden std::map
daha hızlı bir std::unordered_map
ekleniyor ... yani WAT? std::map
daha kötü bir yerelliğe sahiptir (ağaç ve dizi), daha fazla ayırma yapması gerekir (ekleme başına, yeniden çalıştırma başına + artı her çarpışma için ~ 1) ve en önemlisi: başka bir algoritmik karmaşıklığa sahiptir (O (logn) ve O (1))!
SIZE
.