Önemsiz anahtarlarda haritayı unordered_map üzerinde kullanmanın herhangi bir avantajı var mı?


371

unordered_mapC ++ ile ilgili son bir konuşma , aramanın etkinliği nedeniyle daha önce unordered_mapkullandığım çoğu durumda kullanmam gerektiğini anlamamı sağladı map( amortisör O (1) ve O (log n) ). Çoğu zaman bir harita kullanıyorum ya intda std::stringanahtar türü olarak kullanıyorum; bu nedenle, hash fonksiyonunun tanımı ile ilgili bir sorunum yok. Ne kadar çok düşünürsem, basit tiplere sahip tuşlar için bir std::mapover- in kullanmanın herhangi bir nedenini bulamadığımı fark ettim std::unordered_map- arayüzlere bir göz attım ve herhangi bir şey bulamadım kodumu etkileyecek önemli farklılıklar.

Dolayısıyla soru: kullanmak için herhangi bir gerçek neden yoktur std::mapüzerinde std::unordered_mapgibi basit tiplerinin durumunda intve std::string?

Kesinlikle programlama açısından soruyorum - tam olarak standart olarak kabul edilmediğini ve taşıma ile ilgili sorunlara yol açabileceğini biliyorum.

Ayrıca, doğru cevaplardan birinin daha küçük bir ek yük nedeniyle "daha küçük veri kümeleri için daha verimli" olabileceğini umuyorum (bu doğru mu?) - Bu yüzden soruyu miktarın tuşları önemsiz değildir (> 1.024).

Düzenleme: duh, bariz unuttum (teşekkürler GMan!) - evet, haritalar tabii ki sipariş edilir - Bunu biliyorum ve diğer nedenleri arıyorum.


22
Bu soruyu röportajlarda sormayı seviyorum: "Hızlı sıralama ne zaman kabarcık türünden daha iyidir?" Sorunun cevabı, karmaşıklık teorisinin pratik uygulaması hakkında bilgi verir ve sadece O (1) gibi düz siyah beyaz ifadeler O (n) veya O (k) O (logn) vb. ..

42
@Beh, sanırım "ne zaman kabarcık sıralaması hızlı sıralamasından daha iyi" demek istedim: P
Kornel Kisielewicz

2
Akıllı bir işaretçi önemsiz bir anahtar olabilir mi?
thomthom

Haritanın avantajlı olduğu durumlardan biri: stackoverflow.com/questions/51964419/…
anilbey

Yanıtlar:


399

Unutmayın map, öğeleri düzenli tutar. Eğer pes edemezsen, tabii ki kullanamazsın unordered_map.

Akılda tutulması gereken başka bir şey, unordered_mapgenellikle daha fazla bellek kullanmasıdır. mapsadece birkaç ev tutma işaretçisi ve her nesne için hafıza var. Aksine, unordered_mapbüyük bir dizi (bunlar bazı uygulamalarda oldukça büyük olabilir) ve daha sonra her nesne için ek bellek vardır. Eğer bellek farkında olmak mapgerekiyorsa, daha iyi kanıtlamak gerekir, çünkü geniş bir dizi yok.

Bu yüzden, saf arama erişimine ihtiyacınız varsa unordered_map, bunun yolunun olduğunu söyleyebilirim . Ama her zaman takaslar vardır ve eğer bunları karşılayamazsanız, o zaman kullanamazsınız.

Sadece kişisel deneyimlerime dayanarak, bir ana varlık arama tablosunda kullanmak unordered_mapyerine performansta (elbette ölçülen) muazzam bir iyileşme buldum map.

Öte yandan, elemanları tekrar tekrar takıp çıkarmanın çok daha yavaş olduğunu gördüm. Nispeten statik bir eleman koleksiyonu için harika, ancak tonlarca ekleme ve silme işlemi yapıyorsanız, karma + kova toplanıyor gibi görünüyor. (Not, bu birçok yinelemenin üzerindeydi.)


3
Unordered_map ve map (veya vector vs list) gibi büyük (r) bellek bloğu özelliği hakkında bir şey daha, varsayılan işlem yığını (burada Windows konuşuyor) serileştirilir. Çok iş parçacıklı bir uygulamada blokları büyük miktarlarda tahsis etmek çok pahalıdır.
ROAR

4
RA: Belirli bir program için önemli olduğunu düşünüyorsanız, herhangi bir konteynırla birlikte kendi ayırıcı tipinizle kontrol edebilirsiniz.

9
unordered_mapBaşlangıçta bunu biliyor ve rezerve ediyorsanız, hala birçok eklemenin cezasını ödüyor musunuz? Diyelim ki, arama tablosunu oluşturduğunuzda yalnızca bir kez ekliyorsunuz ve daha sonra yalnızca tablodan okuyorsunuz.
thomthom

3
@thomthom Görebildiğim kadarıyla performans açısından herhangi bir ceza olmamalı. Performansın isabet almasının nedeni, dizi çok büyürse, tüm öğelerin yeniden canlandırılmasıdır. Rezerv çağırırsanız, mevcut öğeleri potansiyel olarak yeniden şekillendirir, ancak başlangıçta çağırırsanız, en azından cplusplus.com/reference/unordered_map/unordered_map/reserve
Richard Fung

6
Bellek açısından bunun tam tersi olduğundan eminim. Sıralanmamış bir kapsayıcı için varsayılan 1.0 yük faktörünü varsayarsak: kova için öğe başına bir işaretçi ve kovadaki sonraki öğe için öğe başına bir işaretçi vardır, bu nedenle her öğe için iki işaretçi artı veri elde edersiniz. Sıralı bir kap için, diğer taraftan, tipik bir RB ağacı uygulaması şunları içerecektir: üç işaretçi (sol / sağ / üst) artı hizalama nedeniyle bir sözcük gerektiren bir renk biti. Bu, her öğe için dört işaretçi artı veri.
Yakov Galka

126

Uygulamalarınızın std::mapve std::unordered_mapuygulamalarınızın hızını karşılaştırmak istiyorsanız , Google'ın zamanlamak için bir time_hash_map programı olan sparsehash projesini kullanabilirsiniz . Örneğin, x86_64 Linux sisteminde gcc 4.4.2 ile

$ ./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)

2
Görünüşe göre harita, operasyonların çoğunda haritayı atıyor.
Michael IV

7
sparsehash artık mevcut değil. silindi veya kaldırıldı.
Kullanıcı9102d82

1
@ User9102d82 Bu soruyu bir waybackmachine bağlantısına başvurmak üzere düzenledim .
andreee

Diğerlerinin zamanın yanı sıra diğer sayıları da fark etmelerini sağlamak için: Bu testler int olarak 4 baytlık nesnelerle / veri yapılarıyla yapıldı. Daha fazla karma gerektiren veya daha büyük bir şey saklarsanız (kopyalama işlemlerini daha ağır hale getirir), standart haritanın bir avantajı olabilir!
AlexGeorg

82

Ben kabaca GMan aynı noktayı yankı: kullanım türüne bağlı olarak, (VS 2008 SP1 dahil uygulama kullanarak std::map) daha hızlı olabilir (ve genellikle std::tr1::unordered_map).

Akılda tutulması gereken birkaç karmaşık faktör vardır. Örneğin, std::mapanahtarları karşılaştırıyorsunuz, yani ağacın sağ ve sol alt dallarını ayırt etmek için yalnızca bir anahtarın başlangıcına yeterince bakıyorsunuz. Deneyimlerime göre, tüm bir tuşa baktığınız neredeyse tek zaman, int gibi tek bir komutta karşılaştırabileceğiniz bir şey kullanıyorsanız. Std :: string gibi daha tipik bir anahtar türüyle, genellikle yalnızca birkaç karakteri karşılaştırırsınız.

Buna karşılık, iyi bir karma işlevi her zaman tüm tuşa bakar . IOW, tablo araması sürekli karmaşıklık olsa bile, karma değerinin kabaca doğrusal karmaşıklığı vardır (anahtarın uzunluğuna rağmen, öğe sayısı değil). Anahtar olarak uzun dizelerle, bir std::maparama unordered_mapbile başlamasından önce bir aramayı bitirebilir .

Hash tabloları yeniden boyutlandırma birkaç yöntem vardır İkincisi, çoğu oldukça yavaş - aramaları olmadıkça o noktaya oldukça eklemeler ve silmeler daha sık, std :: map genellikle daha hızlı olacaktır std::unordered_map.

Tabii ki, bir önceki sorunuzun yorumunda bahsettiğim gibi, bir ağaç tablosu da kullanabilirsiniz. Bunun hem avantajları hem de dezavantajları vardır. Bir yandan, en kötü durumu bir ağacınkiyle sınırlar. Ayrıca hızlı ekleme ve silme işlemlerine izin verir, çünkü (en azından bunu yaptığımda) sabit boyutlu bir tablo kullandım. Ortadan kaldırmak , tüm tablo boyutlandırma size çok daha basit ve tipik hızlı karma tablosunu saklamanızı sağlar.

Başka bir nokta: karma ve ağaç tabanlı haritalara ilişkin gereksinimler farklıdır. Karma, açık bir şekilde bir karma işlevi ve sıralı haritaların karşılaştırmadan daha az bir karşılaştırma gerektirdiği bir eşitlik karşılaştırması gerektirir. Tabii ki bahsettiğim hibrit her ikisini de gerektirir. Tabii ki, yaygın olarak bir dize anahtar olarak kullanıldığında, bu gerçekten bir sorun değildir, ancak bazı anahtar türleri, karma işleminden daha iyi sipariş verir (veya tersi).


2
Karma yeniden boyutlandırma, dynamic hashingher öğe eklediğinizde bir geçiş dönemi geçirmekten oluşan tekniklerle de azaltılabilir k. Tabii ki, bu geçiş sırasında 2 farklı tablo aramak zorunda ...
Matthieu M.

2
Msgstr "Uzun dizeler anahtar olarak kullanıldığında, bir std :: map, unordered_map aramaya başlamadan önce aramayı bitirebilir." - anahtar koleksiyonda yoksa. Eğer mevcutsa, eşleşmeyi onaylamak için elbette tam uzunluk karşılaştırılmalıdır. Ancak aynı şekilde unordered_mapbir karma eşleşmeyi tam bir karşılaştırmayla onaylamak gerekir, bu nedenle hepsi, arama sürecinin hangi bölümlerini zıtlaştırdığınıza bağlıdır.
Steve Jessop

2
genellikle hash işlevini verilerin bilgisine dayalı olarak değiştirebilirsiniz. örneğin, uzun dizeleriniz son 20
baytta

56

@Jerry Coffin, sıralı haritanın uzun dizelerde performans artışları göstereceğini öneren cevaptan ilgimi çekti, bazı deneylerden sonra ( hamurdan indirilebilir ), bunun sadece koleksiyonlar için geçerli olduğunu gördüm rastgele dizelerden, harita sıralı bir sözlükle başlatıldığında (hatırı sayılır miktarda önek-çakışma içeren kelimeler içeren), muhtemelen değer elde etmek için gerekli ağaç derinliğinin artması nedeniyle bu kural bozulur. Sonuçlar aşağıda gösterilmektedir, 1. sayı sütunu ekleme süresidir, ikincisi getirme süresidir.

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

2
Test için teşekkürler. Gürültüyü ölçmediğimizden emin olmak için her işlemi birçok kez yapmak üzere değiştirdim (ve harita yerine 1 yerine sayacı yerleştirdim). Farklı sayıdaki tuşlar (2 ila 1000) ve haritada ~ 100 tuşa kadar koştum , özellikle tamsayı tuşlar için std::mapgenellikle daha iyi performans gösteriyor std::unordered_map, ancak ~ 100 tuşlar kenarını kaybediyor ve std::unordered_mapkazanmaya başlıyor. Zaten sipariş edilmiş bir diziyi a'ya eklemek std::mapçok kötü, en kötü senaryoyu alacaksınız (O (N)).
Andreas Magnusson

30

Sadece şunu belirtecektim ki ... birçok çeşit var unordered_map.

Hash haritasındaki Wikipedia makalesine bakın . Hangi uygulamanın kullanıldığına bağlı olarak, arama, yerleştirme ve silme açısından özellikler önemli ölçüde değişebilir.

Ve STL'nin eklenmesiyle beni en çok endişelendiren şey unordered_map: Policyyoldan gideceklerinden şüphelendiğim için belirli bir uygulamayı seçmeleri gerekecek ve bu nedenle ortalama kullanım için bir uygulama ile sıkışıp kalacağız. diğer durumlar ...

Örneğin, bazı karma haritaların doğrusal yeniden şekillendirmesi vardır, burada tüm karma haritayı bir kerede yeniden şekillendirmek yerine, her eklemede maliyetin amorti edilmesine yardımcı olan bir kısım yeniden şekillenir.

Başka bir örnek: bazı karma haritalar bir grup için basit bir düğüm listesi kullanır, diğerleri bir harita kullanır, diğerleri ise düğüm kullanmaz, ancak en yakın yuvayı bulur ve son olarak bazıları düğüm listesini kullanır, ancak son erişilen öğeyi yeniden sıralar ön tarafta (önbellekleme gibi).

Bu yüzden şu anda ( std::mapbelki loki::AssocVectordonmuş veri setleri için) veya

Beni yanlış anlamayın, kullanmak istiyorum std::unordered_mapve gelecekte olabilirim, ancak bunu uygulamanın tüm yollarını ve sonuçta ortaya çıkan çeşitli performansları düşündüğünüzde böyle bir konteynerin taşınabilirliğine "güvenmek" zordur. bu.


17
+1: geçerli nokta - kendi uygulamamı kullanırken hayat daha kolaydı - en azından nerede emildiğini biliyordum :>
Kornel Kisielewicz

25

Burada yeterince bahsedilmeyen önemli farklılıklar:

  • mapyineleyicileri tüm öğelere sabit tutar, C ++ 17'de mapyineleyicileri kendilerine geçersiz kılmadan (ve herhangi bir potansiyel ayırma olmadan düzgün bir şekilde uygulanırsa) öğeleri birinden diğerine bile taşıyabilirsiniz .
  • map tekli operasyonların zamanlamaları genellikle daha büyük tahsislere ihtiyaç duymadıkları için daha tutarlıdır.
  • unordered_mapstd::hashlibstdc ++ 'da uygulandığı gibi kullanılması , güvenilmeyen girdiyle beslenirse DoS'a karşı savunmasızdır (MurmurHash2'yi sabit bir tohumla kullanır - tohumlamanın gerçekten yardımcı olacağını değil, bkz. https://emboss.github.io/blog/2012/12/14/ kırma-üfürüm-karma-sel-dos-yeniden yüklendi / ).
  • Sipariş vermek, range 42 tuşuyla tüm öğelerin yinelenmesi gibi etkili aralık aramaları sağlar.

14

Karma tablolar, küçük kaplar için önemli hale gelen yaygın harita uygulamalarından daha yüksek sabitlere sahiptir. Maksimum boyut 10, 100 veya belki 1.000 veya daha fazladır? Sabitler her zamankiyle aynıdır, ancak O (log n) O (k) 'ye yakındır. (Logaritmik karmaşıklığın hala gerçekten iyi olduğunu unutmayın.)

Karma işlevini iyi yapan şey verilerinizin özelliklerine bağlıdır; bu yüzden özel bir karma işlevine bakmayı planlamıyorsam (ancak daha sonra ve her şeyin yakınında lanet tanımladığımdan beri kolayca fikrimi değiştirebilirim) ve birçok veri kaynağı için düzgün bir şekilde çalışmak için varsayılanlar seçilse de, Bu durumda bir karma tablo yerine harita varsayılan hala başlangıçta bir yardım yeterli olması için doğanın.

Ayrıca, bu şekilde diğer (genellikle UDT) türleri için bir karma işlevi yazmayı düşünmek zorunda değilsiniz ve sadece op <(yine de istediğiniz) yazın.


@Roger, unordered_map öğesinin en iyi eşlendiği öğelerin yaklaşık miktarını biliyor musunuz? Muhtemelen bunun için bir test yazacağım, yine de ... (+1)
Kornel Kisielewicz

1
@Kornel: Çok fazla zaman almaz; Testlerim yaklaşık 10.000 elementle yapıldı. Biz istiyorsanız gerçekten doğru grafiği, bir uygulanması bakabiliriz mapve biri unordered_mapbelirli bir platform ve bazı önbellek boyutu ile, ve karmaşık bir analiz yapmak. : P
GManNickG

Uygulama ayrıntılarına, derleme zamanı ayarlama parametrelerine (kendi uygulamanızı yazıyorsanız desteklemesi kolay) ve hatta testler için kullanılan makineye bağlıdır. Tıpkı diğer konteynırlarda olduğu gibi, komite sadece geniş gereksinimleri belirler.

13

Diğer cevaplarda nedenler verilmiştir; işte başka.

std :: map (dengeli ikili ağaç) işlemleri O (log n) ve en kötü O (log n) amortismana tabi tutulur. std :: unordered_map (hash tablosu) işlemleri, O (1) ve en kötü O (n) amortismana tabi tutulur.

Bunun pratikte nasıl oynandığı, karma tablonun, uygulamanızın tolere edebileceği veya olmayabileceği bir O (n) işlemi ile arada bir "hıçkırır" olmasıdır. Eğer tahammül edemezse, std :: map üzerinden std :: unordered_map'yi tercih edersiniz.


12

özet

Siparişin önemli olmadığını varsayarsak:

  • Büyük tabloyu bir kez oluşturacak ve çok sayıda sorgu yapacaksanız, std::unordered_map
  • Küçük bir tablo oluşturacaksanız (100 öğenin altında olabilir) ve çok sayıda sorgu yapacaksanız, kullanın std::map. Bunun nedeni, üzerinde okumalar olmasıdır O(log n).
  • Sonra değişim tablosu çok yapacaksanız olabilir std::map iyi bir seçenektir.
  • Şüpheniz varsa kullanın std::unordered_map.

Tarihsel Bağlam

Çoğu dilde, sıralanmamış harita (karma tabanlı sözlükler olarak da bilinir) varsayılan haritadır, ancak C ++ 'da varsayılan harita olarak sipariş vermiş olursunuz. Bu nasıl oldu? Bazı insanlar yanlışlıkla C ++ komitesinin bu kararı benzersiz bilgelikleriyle aldığını varsayıyor, ancak gerçek ne yazık ki bundan daha çirkin.

Yaygın olduğu inanılan onlar nasıl uygulanabileceğini üzerinde çok fazla parametre değildir çünkü C ++ varsayılan olarak sipariş harita ile sona erdi. Öte yandan, karma tabanlı uygulamalar hakkında konuşulacak tonlarca şey vardır. Standardizasyondaki kilitlenmeleri önlemek için, sadece sipariş edilen harita ile anlaştılar. 2005 civarında, birçok dilde karma tabanlı uygulama iyi uygulanmıştı ve bu nedenle komitenin yeni kabul etmesi daha kolaydı std::unordered_map. Mükemmel bir dünyada, std::mapsırasız olurdu std::ordered_mapve ayrı bir tip olurdu .

Verim

Aşağıdaki iki grafik kendileri için konuşmalıdır ( kaynak ):

resim açıklamasını buraya girin

resim açıklamasını buraya girin


İlginç veriler; testlerinize kaç platform eklediniz?
Toby Speight

1
std :: unordered_map her zaman burada yayınladığınız 2 görüntüye göre std :: map'den daha iyi performans gösterdiğinden neden std :: map'i küçük tablo için kullanmalıyım?
ricky

Grafik, 0,13M veya daha fazla elemanın performansını gösterir. Küçük (<100 olabilir) öğeleriniz varsa O (log n) düzenlenmemiş haritadan daha küçük olabilir.
Shital Shah

10

Son zamanlarda 50000 birleştirme ve sıralama yapan bir test yaptım. Dize anahtarları aynıysa, bayt dizesini birleştirin. Ve nihai çıktı sıralanmalıdır. Bu, her ekleme için bir arama içerir.

İçin mapuygulanması, bu işi bitirmek için 200 ms alır. unordered_map+ İçin, ekleme mapiçin 70 ms ve unordered_mapekleme için 80 ms sürer map. Böylece hibrit uygulama 50 ms daha hızlıdır.

Kullanmadan önce iki kez düşünmeliyiz map. Verilerin yalnızca programınızın nihai sonucunda sıralanmasına ihtiyacınız varsa, karma bir çözüm daha iyi olabilir.


0

Yukarıdakilerin tümüne küçük bir ekleme:

Daha iyi kullanım map, onlar sıralanır olarak, aralığına göre elemanlarını almak gerekir ve mümkün sadece yinelerler üzerlerinden bir sınır diğerine ne zaman.


-1

Gönderen: http://www.cplusplus.com/reference/map/map/

"Dahili olarak, bir haritadaki öğeler, dahili karşılaştırma nesnesiyle (Karşılaştırma türünde) belirtilen belirli bir katı zayıf sıralama ölçütünün ardından her zaman anahtarına göre sıralanır.

harita kapsayıcıları, anahtarlarına göre tek tek öğelere erişmek için genellikle unordered_map kapsayıcılarından daha yavaştır, ancak siparişlerine göre alt kümelerde doğrudan yinelemeye izin verir. "

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.