Ein kürzlich unordered_map
in C ++ veröffentlichter Vortrag hat mir klar gemacht, dass ich ihn aufgrund der Effizienz der Suche ( amortisiertes O (1) vs. O (log n) ) unordered_map
für die meisten Fälle verwenden sollte, in denen ich ihn map
zuvor verwendet habe . Meistens verwende ich eine Karte, entweder oder als Schlüsseltyp. Daher habe ich keine Probleme mit der Definition der Hash-Funktion. Je mehr ich darüber nachdachte, desto mehr wurde mir klar, dass ich bei Schlüsseln mit einfachen Typen keinen Grund finden kann, ein Over-A zu verwenden. Ich habe mir die Schnittstellen angesehen und keine gefunden signifikante Unterschiede, die sich auf meinen Code auswirken würden.int
std::string
std::map
std::unordered_map
Daraus ergibt sich die Frage: Gibt es eine wahre Grund für die Verwendung std::map
über std::unordered_map
im Fall von einfachen Typen wie int
und std::string
?
Ich frage aus rein programmtechnischer Sicht - ich weiß, dass es nicht vollständig als Standard betrachtet wird und dass es Probleme mit der Portierung geben kann.
Außerdem erwarte ich, dass eine der richtigen Antworten "es ist effizienter für kleinere Datensätze" aufgrund eines geringeren Overheads ist (stimmt das?) - daher möchte ich die Frage auf Fälle beschränken, in denen die Menge von Schlüssel sind nicht trivial (> 1 024).
Edit: duh, ich habe das Offensichtliche vergessen (danke GMan!) - ja, Karten sind natürlich bestellt - das weiß ich und suche nach anderen Gründen.
quelle
Antworten:
Vergessen Sie nicht, dass
map
die Elemente geordnet bleiben. Wenn Sie das nicht aufgeben können, können Sie es natürlich nicht verwendenunordered_map
.Beachten Sie außerdem, dass im
unordered_map
Allgemeinen mehr Speicher benötigt wird.map
hat nur ein paar Haushaltszeiger und Speicher für jedes Objekt. Im Gegensatz dazuunordered_map
hat ein großes Array (diese können in einigen Implementierungen ziemlich groß werden) und dann zusätzlichen Speicher für jedes Objekt. Wenn Sie speicherbewusst sein müssen,map
sollte sich dies als besser erweisen, da das große Array fehlt.Wenn Sie also einen reinen Lookup-Retrieval benötigen,
unordered_map
ist dies der richtige Weg. Aber es gibt immer Kompromisse, und wenn Sie sie sich nicht leisten können, können Sie sie nicht nutzen.Nur aus persönlicher Erfahrung heraus fand ich eine enorme Verbesserung der Leistung (natürlich gemessen) bei der Verwendung
unordered_map
anstellemap
einer Nachschlagetabelle für Hauptentitäten.Andererseits stellte ich fest, dass das wiederholte Einfügen und Entfernen von Elementen viel langsamer war. Es ist großartig für eine relativ statische Sammlung von Elementen, aber wenn Sie Tonnen von Einfügungen und Löschungen vornehmen, scheint sich das Hashing + Bucketing zu summieren. (Beachten Sie, dass dies über viele Iterationen hinweg war.)
quelle
unordered_map
und diese zu Beginn reservieren - zahlen Sie trotzdem eine Strafe für viele Einfügungen? Angenommen, Sie fügen nur einmal ein, wenn Sie die Nachschlagetabelle erstellt haben - und lesen später nur noch daraus.Wenn Sie die Geschwindigkeit Ihrer
std::map
und derstd::unordered_map
Implementierungen vergleichen möchten , können Sie das Sparsehash- Projekt von Google verwenden , das über ein time_hash_map-Programm verfügt, um die Zeit zu bestimmen. Zum Beispiel mit gcc 4.4.2 auf einem x86_64-Linux-Systemquelle
Ich würde ungefähr den gleichen Punkt wiederholen, den GMan gemacht hat: Je nach Art der Verwendung
std::map
kann (und ist) dies schneller alsstd::tr1::unordered_map
(unter Verwendung der in VS 2008 SP1 enthaltenen Implementierung).Es gibt einige komplizierende Faktoren, die zu beachten sind. Zum Beispiel
std::map
vergleichen Sie in Schlüssel, was bedeutet, dass Sie immer nur genug vom Anfang eines Schlüssels betrachten, um zwischen dem rechten und dem linken Unterzweig des Baums zu unterscheiden. Nach meiner Erfahrung ist es fast das einzige Mal, dass Sie einen ganzen Schlüssel betrachten, wenn Sie so etwas wie int verwenden, das Sie in einer einzigen Anweisung vergleichen können. Bei einem typischeren Schlüsseltyp wie std :: string vergleichen Sie häufig nur wenige Zeichen oder so.Eine anständige Hash-Funktion hingegen betrachtet immer den gesamten Schlüssel. IOW, selbst wenn die Tabellensuche eine konstante Komplexität aufweist, weist der Hash selbst eine ungefähr lineare Komplexität auf (allerdings auf der Länge des Schlüssels, nicht auf der Anzahl der Elemente). Mit langen Zeichenfolgen als Schlüssel kann
std::map
eine Suche beendet werden, bevor eine Sucheunordered_map
überhaupt gestartet wird .Zweitens, obwohl es verschiedene Methoden zum Ändern der Größe von Hash-Tabellen gibt, sind die meisten davon ziemlich langsam - bis zu dem Punkt, dass std :: map oft schneller ist als , wenn Suchvorgänge nicht wesentlich häufiger sind als Einfügungen und Löschungen
std::unordered_map
.Natürlich können Sie, wie ich im Kommentar zu Ihrer vorherigen Frage erwähnt habe, auch eine Baumtabelle verwenden. Dies hat sowohl Vor- als auch Nachteile. Einerseits beschränkt es den schlimmsten Fall auf den eines Baumes. Es ermöglicht auch ein schnelles Einfügen und Löschen, da ich (zumindest wenn ich es getan habe) eine Tabelle mit fester Größe verwendet habe. Die Beseitigung aller Tabelle Redimensionierung können Sie Ihre Hash - Tabelle viel einfacher und in der Regel schneller halten.
Ein weiterer Punkt: Die Anforderungen für Hashing und baumbasierte Karten sind unterschiedlich. Das Hashing erfordert offensichtlich eine Hash-Funktion und einen Gleichheitsvergleich, wobei geordnete Karten einen weniger als Vergleich erfordern. Natürlich erfordert der von mir erwähnte Hybrid beides. Für den allgemeinen Fall, dass eine Zeichenfolge als Schlüssel verwendet wird, ist dies natürlich kein wirkliches Problem, aber einige Arten von Schlüsseln passen besser zur Reihenfolge als zum Hashing (oder umgekehrt).
quelle
dynamic hashing
Techniken gedämpft werden, die darin bestehen, eine Übergangszeit zu haben, in der Sie jedes Mal, wenn Sie ein Element einfügen, auchk
andere Elemente erneut aufbereiten . Natürlich bedeutet dies, dass Sie während des Übergangs 2 verschiedene Tabellen durchsuchen müssen ...unordered_map
eine Hash-Übereinstimmung mit einem vollständigen Vergleich bestätigen, damit alles davon abhängt, welchen Teilen des Suchprozesses Sie gegenüberstehen.Ich war fasziniert von der Antwort von @Jerry Coffin, die darauf hinwies, dass die geordnete Karte bei langen Zeichenfolgen Leistungssteigerungen aufweisen würde. Nach einigen Experimenten (die von Pastebin heruntergeladen werden können ) habe ich festgestellt, dass dies nur für Sammlungen zu gelten scheint Bei zufälligen Zeichenfolgen bricht diese Regel zusammen, wenn die Karte mit einem sortierten Wörterbuch initialisiert wird (das Wörter mit erheblichen Präfixüberlappungen enthält), vermutlich aufgrund der erhöhten Baumtiefe, die zum Abrufen des Werts erforderlich ist. Die Ergebnisse sind unten gezeigt, die erste Zahlenspalte ist die Einfügezeit, die zweite ist die Abrufzeit.
quelle
std::map
normalerweise besser abschneidenstd::unordered_map
, insbesondere bei Ganzzahlschlüsseln, aber bei ~ 100 Schlüsseln scheint es, als ob es seinen Rand verliert undstd::unordered_map
anfängt zu gewinnen. Das Einfügen einer bereits geordneten Sequenz in einestd::map
ist sehr schlecht. Sie erhalten das Worst-Case-Szenario (O (N)).Ich möchte nur darauf hinweisen, dass ... es viele Arten von
unordered_map
s gibt.Schlagen Sie den Wikipedia-Artikel auf der Hash-Karte nach. Je nachdem, welche Implementierung verwendet wurde, können die Merkmale hinsichtlich Nachschlagen, Einfügen und Löschen erheblich variieren.
Und das ist es, was mich am meisten beunruhigt, wenn ich die
unordered_map
STL hinzufüge: Sie müssen eine bestimmte Implementierung auswählen, da ich bezweifle, dass sie diePolicy
Straße hinuntergehen werden , und so werden wir bei einer Implementierung für den durchschnittlichen Gebrauch und nichts für bleiben die anderen Fälle ...Zum Beispiel haben einige Hash-Maps eine lineare Aufbereitung, wobei anstelle einer erneuten Aufbereitung der gesamten Hash-Map bei jeder Einfügung ein Teil erneut aufbereitet wird, was zur Amortisation der Kosten beiträgt.
Ein weiteres Beispiel: Einige Hash-Maps verwenden eine einfache Liste von Knoten für einen Bucket, andere verwenden eine Map, andere verwenden keine Knoten, sondern suchen den nächsten Steckplatz, und einige verwenden eine Liste von Knoten, ordnen sie jedoch so an, dass das Element, auf das zuletzt zugegriffen wird ist vorne (wie ein Caching-Ding).
Im Moment bevorzuge ich eher das
std::map
oder vielleicht einloki::AssocVector
(für eingefrorene Datensätze).Verstehen Sie mich nicht falsch, ich würde das gerne verwenden,
std::unordered_map
und ich kann es in Zukunft tun , aber es ist schwierig, der Portabilität eines solchen Containers zu "vertrauen", wenn Sie über alle Implementierungsmöglichkeiten und die verschiedenen daraus resultierenden Leistungen nachdenken von diesem.quelle
Wesentliche Unterschiede, die hier nicht ausreichend erwähnt wurden:
map
Hält Iteratoren für alle Elemente stabil. In C ++ 17 können Sie sogar Elemente von einemmap
zum anderen verschieben, ohne die Iteratoren für sie ungültig zu machen (und bei ordnungsgemäßer Implementierung ohne potenzielle Zuordnung).map
Die Zeitabläufe für einzelne Vorgänge sind in der Regel konsistenter, da sie niemals große Zuordnungen erfordern.unordered_map
Die Verwendungstd::hash
wie in libstdc ++ implementiert ist für DoS anfällig, wenn sie mit nicht vertrauenswürdigen Eingaben gespeist wird (es wird MurmurHash2 mit einem konstanten Startwert verwendet - nicht, dass das Startwert wirklich helfen würde, siehe https://emboss.github.io/blog/2012/12/14/ Breaking-Murmeln-Hash-Flooding-Dos-Reloaded / ).quelle
Hash-Tabellen haben höhere Konstanten als gängige Map-Implementierungen, die für kleine Container von Bedeutung sind. Die maximale Größe beträgt 10, 100 oder vielleicht sogar 1.000 oder mehr? Konstanten sind die gleichen wie immer, aber O (log n) liegt nahe bei O (k). (Denken Sie daran, dass die logarithmische Komplexität immer noch sehr gut ist.)
Was eine gute Hash-Funktion ausmacht, hängt von den Eigenschaften Ihrer Daten ab. Wenn ich also nicht vorhabe, eine benutzerdefinierte Hash-Funktion zu betrachten (aber meine Meinung später sicherlich ändern kann, und zwar leicht, da ich fast alles tippe), und obwohl die Standardeinstellungen so gewählt sind, dass sie für viele Datenquellen eine anständige Leistung erbringen, finde ich die geordnete Die Art der Karte ist anfangs eine ausreichende Hilfe, sodass ich in diesem Fall immer noch standardmäßig eine Karte anstelle einer Hash-Tabelle verwende.
Außerdem müssen Sie auf diese Weise nicht einmal daran denken, eine Hash-Funktion für andere (normalerweise UDT) Typen zu schreiben, und einfach op <schreiben (was Sie sowieso wollen).
quelle
map
und eineunordered_map
mit einer bestimmten Plattform und einer bestimmten Cache-Größe ansehen und eine komplexe Analyse durchführen. : PGründe wurden in anderen Antworten angegeben; hier ist noch einer.
std :: map-Operationen (ausgeglichener Binärbaum) werden mit O (log n) und Worst-Case-O (log n) abgeschrieben. std :: unordered_map (Hash-Tabelle) Operationen werden amortisiert O (1) und Worst-Case O (n).
Wie sich dies in der Praxis auswirkt, ist, dass die Hash-Tabelle gelegentlich mit einer O (n) -Operation "Schluckauf" hat, was Ihre Anwendung möglicherweise tolerieren kann oder nicht. Wenn es nicht toleriert werden kann, bevorzugen Sie std :: map gegenüber std :: unordered_map.
quelle
Zusammenfassung
Vorausgesetzt, die Bestellung ist nicht wichtig:
std::unordered_map
std::map
. Dies liegt daran, dass darauf gelesen wirdO(log n)
.std::map
ist dies möglicherweise eine gute Option.std::unordered_map
.Historischer Zusammenhang
In den meisten Sprachen sind ungeordnete Karten (auch als Hash-basierte Wörterbücher bezeichnet) die Standardkarte. In C ++ erhalten Sie jedoch eine geordnete Karte als Standardkarte. Wie ist das passiert? Einige Leute gehen fälschlicherweise davon aus, dass das C ++ - Komitee diese Entscheidung in ihrer einzigartigen Weisheit getroffen hat, aber die Wahrheit ist leider hässlicher.
Es wird allgemein angenommen, dass C ++ standardmäßig eine geordnete Karte hat, da es nicht zu viele Parameter gibt, wie sie implementiert werden können. Auf der anderen Seite gibt es bei Hash-basierten Implementierungen jede Menge zu besprechen. Um Blockaden bei der Standardisierung zu vermeiden, kamen sie nur mit der geordneten Karte zurecht . Um 2005 hatten viele Sprachen bereits gute Implementierungen der Hash-basierten Implementierung, so dass es für das Komitee einfacher war, neue zu akzeptieren
std::unordered_map
. In einer perfekten Weltstd::map
wäre ungeordnet gewesen und wir hättenstd::ordered_map
als separaten Typ.Performance
Die folgenden zwei Grafiken sollten für sich selbst sprechen ( Quelle ):
quelle
Ich habe kürzlich einen Test durchgeführt, bei dem 50000 zusammengeführt und sortiert werden. Das heißt, wenn die Zeichenfolgenschlüssel identisch sind, führen Sie die Bytezeichenfolge zusammen. Und die endgültige Ausgabe sollte sortiert werden. Dies beinhaltet also eine Suche nach jeder Einfügung.
Für die
map
Implementierung dauert es 200 ms, um den Job zu beenden. Für dasunordered_map
+map
dauert dasunordered_map
Einfügen 70 ms und das Einfügen 80 msmap
. Die Hybridimplementierung ist also 50 ms schneller.Wir sollten zweimal überlegen, bevor wir das verwenden
map
. Wenn Sie nur die Daten im Endergebnis Ihres Programms sortieren müssen, ist eine Hybridlösung möglicherweise besser.quelle
Kleine Ergänzung zu allen oben genannten:
Besser verwenden
map
, wenn Sie Elemente nach Bereich abrufen müssen, da sie sortiert sind und Sie sie einfach von einer Grenze zur anderen durchlaufen können.quelle
Von: http://www.cplusplus.com/reference/map/map/
"Intern werden die Elemente in einer Karte immer nach ihrem Schlüssel sortiert, und zwar nach einem bestimmten strengen Kriterium für schwache Ordnungen, das durch das interne Vergleichsobjekt (vom Typ Vergleichen) angegeben wird.
Map-Container sind im Allgemeinen langsamer als unordered_map-Container, um über ihren Schlüssel auf einzelne Elemente zuzugreifen. Sie ermöglichen jedoch die direkte Iteration von Teilmengen basierend auf ihrer Reihenfolge. "
quelle