Wie steigere ich die Leistung in einer Karte-lookup-Schlüssel Typ std::string?
Ich bin mit einem std::map
(VC++ Implementierung) und es ist ein wenig langsam für Suchvorgänge über die Karte find-Methode.
Den key-Typ ist std::string
.
Erhöhe ich die Leistung dieses std::map
lookup über einen benutzerdefinierten Schlüssel an, vergleichen Sie überschreiben für die Karte? Zum Beispiel, vielleicht std::string
< vergleichen nicht berücksichtigt, eine einfache string::size()
vergleichen Sie vor dem Vergleich seine Daten?
Andere Ideen zu beschleunigen das vergleichen?
In meiner situation, die Karte wird immer enthalten < 15 Elemente, aber es wird abgefragt, non-stop und die Leistung entscheidend ist. Vielleicht gibt es eine bessere Datenstruktur, die ich verwenden können, das wäre schneller?
Update: Die Karte enthält die Datei Pfade.
Update2: Die Karte, die Elemente ändern sich oft.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Erste, schalten Sie alle profiling-und DEBUG-Schalter. Diese verlangsamen kann, STL immens.
Wenn, dass ist es nicht, ein Teil des Problems kann sein, dass deine Saiten sind identisch mit denen für die ersten 80-90% des Strings. Das ist nicht schlecht für die Karte, unbedingt, aber es ist für string-Vergleiche. Wenn dies der Fall ist, ist Ihre Suche viel länger dauern kann.
Zum Beispiel in diesem code finden() wird wahrscheinlich Ergebnis in einem paar von Strings vergleicht, aber jede Rückkehr nach vergleichen der ersten Zeichen, bis "david", und dann die ersten drei Zeichen geprüft werden. Also höchstens 5 Zeichen geprüft werden, die pro Anruf.
Auf der anderen Seite, im folgenden code finden() wird wahrscheinlich prüfen Sie 135+ Zeichen:
Das ist, weil die string-Vergleiche, suchen tiefer, um eine übereinstimmung zu finden, da der Anfang jeder string ist das gleiche.
Mit size() in Ihrem Vergleich auf Gleichheit wird nicht viel helfen hier, da Sie Ihre Daten-set ist so klein. Eine std::map ist gehalten sortiert, so können seine Elemente gesucht werden, die mit einer binären Suche. Jeder Aufruf zu finden, sollte weniger als 5 string-Vergleiche für eine miss und einen Durchschnitt von 2 Vergleiche für einen Treffer. Aber es hängt nicht von Ihren Daten. Wenn die meisten Ihrer Pfad-strings unterschiedliche Längen, dann eine Größe überprüfen, wie Motti beschreibt, könnte helfen, eine Menge.
Etwas zu berücksichtigen, wenn das denken von alternativen algorithmen ist, wie viele "hits", die Sie erhalten. Sind die meisten deiner find () - Aufrufe Rückkehr Ende() oder ein Treffer? Wenn die meisten Ihrer finden()s return end() (findet) dann suchst du die gesamte Karte jeder Zeit (2logn string vergleicht).
Hash_map ist eine gute Idee; Sie sollte schneiden Sie Ihre Zeit der Suche in etwa die Hälfte trifft, mehr für findet.
Einen benutzerdefinierten Algorithmus bezeichnet werden kann, weil die Natur des Pfad-strings, vor allem, wenn Sie Ihre Daten-set hat eine gemeinsame Abstammung, wie im obigen code.
Andere Sache zu prüfen ist, wie bekommen Sie Ihre Suchbegriffe. Wenn Sie wiederzuverwenden, kann es helfen, das zu codieren, Sie in etwas, das leichter zu vergleichen. Wenn Sie Sie einmal und werfen Sie Sie Weg, dann diese Codierung Schritt ist wahrscheinlich zu teuer.
Benutzte ich so etwas wie eine Huffman-Codierung Baum einmal (vor langer Zeit) zu optimieren Zeichenfolge sucht. Ein Binär-string-Suche-Baum kann effizienter sein, in einigen Fällen, aber es ist ziemlich teuer für die kleinen Mengen, so wie Euch.
Schließlich, sehen Sie in alternative zu std::map-Implementierungen. Ich habe gehört, schlechte Dinge über einige der VC-stl-code Leistung. Die DEBUG-Bibliothek in allem ist schlimm daran, überprüfen Sie bei jedem Anruf. StlPort verwendet werden, um eine gute alternative, aber ich habe nicht versucht es in ein paar Jahren. Ich habe es immer geliebt Boost zu.
operator[]
in meinemmap<int,...>
).Als Auch, sagte der Betreiber verwendet in einem
set
ist<
nicht==
.Wenn Sie kümmern sich nicht um die Reihenfolge der strings in Ihre
set
Sie können übergeben, dieset
ein benutzerdefiniertes Programm, dass besser abschneidet als die reguläre weniger als.Zum Beispiel, wenn eine Menge von Ihren Saiten haben ähnliche Präfixe (aber Sie variieren in der Länge) können Sie Sortieren durch die string-Länge (da
string.length
ist Konstante Geschwindigkeit).Wenn Sie wollen, so hüten Sie sich vor einem häufigen Fehler:
Dieser operator nicht beibehalten strenge schwache bestellen, können Sie zwei strings, von denen jeder weniger als der andere.
Folgen der Logik, und Sie werden sehen, dass
comp(a, b) == true
undcomp(b, a) == true
.Die richtige Umsetzung ist:
Die erste Sache ist, zu versuchen, mit einer hash_map, wenn das möglich ist - Sie haben Recht, dass die standard-string-vergleichen nicht die erste Prüfung für die Größe (da es vergleicht lexikographisch), aber schreiben Sie Ihre eigenen anzeigen-code ist etwas, das Sie besser aufgehoben wären, zu vermeiden. Aus Ihrer Frage, es klingt wie Sie müssen nicht zu Durchlaufen reicht; in diesem Fall anzeigen, jedoch nichts hash_map nicht.
Es hängt auch davon ab, welche Art von Schlüssel Sie in Ihrer map. Sind Sie in der Regel sehr lange? Auch was bedeutet "ein wenig langsam" bedeuten? Wenn Sie nicht profiliert, der code, es ist durchaus möglich, dass es ein anderer Teil nimmt sich Zeit.
Update: Hmm, den Engpass in Ihrem Programm ist eine map::find, aber die Karte hat immer weniger als 15 Elementen. Dies lässt mich vermuten, dass das Profil war irgendwie irreführend, da eine finden auf einer Karte das kleine sollte nicht langsam sein, überhaupt. In der Tat, eine map::find sollte so schnell, nur der Aufwand zu Profilieren, könnte mehr als die finden sich selbst aufrufen. Ich muss wieder Fragen, sind Sie sicher, dass dies wirklich der Engpass in Ihrem Programm? Sie sagen, die Saiten sind Pfade, aber Sie nicht tun, jede Art von Betriebssystem-Aufrufe, Datei-system-Zugang, Zugriff auf die Festplatte in dieser Schleife? Jeder von denen sollte Größenordnungen langsamer als eine map::find auf eine kleine Karte. Wirklich jeder Weg, um eine Zeichenfolge sollte langsamer als die map::find.
Können Sie versuchen, einen sortierten Vektor (hier ist ein Beispiel), dieser kann sich schneller (du musst Profil, um sicherzustellen, dass natürlich).
Gründe zu glauben, es werde schneller sein:
Gründe zu glauben, es werde langsamer:
string
'sswap
ist effektiv ist und die Größe der Daten klein ist, kann dies nicht ein Problem sein.std::map Komparator ist nicht std::equal_to, es ist std::less, ich bin mir nicht sicher, was der beste Weg, um Kurzschluss eine < vergleichen, so dass es schneller als die eingebauten in einem.
Wenn es immer < 15 elems, vielleicht könnten Sie ein-Taste neben std::string?
Motti hat eine gute Lösung. Allerdings bin ich mir ziemlich sicher, dass für Ihr < 15 Elemente einer Karte ist nicht der richtige Weg, weil sein Aufwand wird immer größer sein als die einer einfachen lookup-Tabelle mit einer geeigneten hashing-Schema. In deinem Fall könnte es sogar sein, genug, um die hash-Länge von allein, und wenn das immer noch produziert Kollisionen, verwenden Sie eine lineare Suche durch alle Einträge die gleiche Länge.
Festzustellen, ob ich Recht habe, ein benchmark ist natürlich erforderlich, aber ich bin ziemlich sicher, dass von Ihrem Ergebnis.
Könnten Sie die pre-computing ein hash für den string und speichern das in Ihren anzeigen. Dabei ergibt sich der Vorteil der hash vergleicht statt der Zeichenfolge vergleicht, die während der Suche durch das std::map Baum.
Dies hat die Vorteile von computing-ein hash-Wert der Zeichenfolge einmal auf dem Bau. Nach dieser, könnten Sie implementieren eine Vergleich-Funktion:
Seit hashes sind nun berechnet
HashedString
Bau, in dem Sie gespeichert sind, die Art und Weise, in der std::map, und so das vergleichen kann sehr schnell passieren (eine Ganzzahl vergleichen) in einen astronomisch hohen Prozentsatz der Zeit, fallen zurück auf standard-string vergleicht, wenn die hashes gleich sind.Vielleicht könnte man Umgekehrt die strings vor der Verwendung als Schlüssel in der map? Das könnte helfen, wenn Sie die ersten Buchstaben der einzelnen Zeichenfolgen identisch sind.
Hier sind einige Dinge, die Sie betrachten können:
0) Bist du sicher, dass dies ist, wo der performance-Flaschenhals ist? Wie die Ergebnisse zu Quantifizieren, Cachegrind, gprof oder sowas? Da Suchvorgänge auf solche smap Karte sollte ziemlich schnell...
1) Sie können das überschreiben der Funktor vergleichen Sie die Schlüssel in std::map<> gibt es einen zweiten template-parameter, das zu tun. Ich bezweifle, dass Sie tun können, viel besser als operator<, aber.
2) Werden die Inhalte der Karte viel verändern? Wenn nicht, und angesichts der sehr kleinen Größe der Karte, die vielleicht ein sortiert vector und binärer Suche, könnte zu besseren Resultaten führen (zum Beispiel, weil Sie nutzen können Speicher Ort besser.
3) Sind die Elemente, die zur Kompilierzeit bekannt? Sie kann eine perfekte hash-Funktion zu verbessern lookup-Zeiten, wenn das der Fall ist. Suche für gperf auf das web.
4) haben Sie eine Menge von lookups, dass scheitern nichts zu finden? Wenn ja, vielleicht zu vergleichen mit der ersten und letzten Elemente in der Sammlung zu beseitigen viele Abweichungen schneller als eine vollständige Suche jedes mal.
Diese wurden schon vorgeschlagen, aber in mehr detail:
5) Da Sie so wenige Saiten, vielleicht könnten Sie einen anderen Schlüssel verwenden. Zum Beispiel, deine keys alle die gleiche Größe? Sie können verwenden Sie eine Klasse, die eine Feste Länge-array von Zeichen? Können Sie konvertieren Sie Ihre strings in zahlen oder Daten, Struktur mit nur zahlen?
Abhängig von der Auslastung Fällen, gibt es einige andere Techniken, die Sie verwenden können. Zum Beispiel haben wir eine Anwendung, die benötigt wird, um mit über eine million verschiedene Datei-Pfade. Das problem mit, dass es Tausende von Objekten, die benötigt werden, um kleine Karten dieser Datei Pfade.
Da das hinzufügen neuer Datei-Pfade zu den Daten-set wurde nur sehr selten durchgeführt, wenn der Pfad wurde Hinzugefügt, um die system -, eine master-Karte durchsucht wurde. Wenn der Pfad nicht gefunden wurde, dann wurde es Hinzugefügt und eine neue sequenziert Ganzzahl ist (beginnend bei 1) zurückgegeben wurde. Wenn der Pfad bereits existiert, dann wird der zuvor zugewiesene integer zurückgegeben wurde. Dann jede map verwaltet von jedes Objekt wurde aus einer Zeichenfolge konvertiert basierte Karte in einen integer anzeigen. Nicht nur, dass dies die Leistung erheblich verbessert, es verringert die Speichernutzung nicht mit so vielen Kopien des strings.
Sicher, dies ist eine sehr spezifische Optimierung. Aber wenn es um performance-Verbesserungen, oft finden Sie sich mit, um maßgeschneiderte Lösungen für spezifische Probleme.
Und ich hasse strings 🙂 Nicht, sind Sie langsam, um zu vergleichen, aber Sie können wirklich trash Ihre CPU-caches auf high-performance-software.
Versuchen, std::tr1::unordered_map (gefunden in der header <tr1/unordered_map>). Dies ist eine hash-map, und während es doesn ' T erhalten eine sortierte Reihenfolge der Elemente, wird wahrscheinlich weit schneller als eine reguläre Karte.
Wenn Ihr compiler nicht unterstützt, TR1, erhalten eine neuere version. MSVC und gcc unterstützen beide TR1, und ich glaube, dass die neuesten Versionen der meisten anderen Compilern auch unterstützen. Leider, eine Menge der Bibliothek Referenz-Websites noch nicht aktualisiert wurde, so TR1 bleibt eine weitgehend unbekannte Stück Technik.
Ich hoffe, dass C++0x ist nicht die gleiche Weise.
EDIT: Beachten Sie, dass die Standard-hashing-Methode für tr1::unordered_map ist tr1::hash, die spezialisiert werden, um die Arbeit auf eine UDT, wahrscheinlich.
Wo Sie haben lange gemeinsame Teilfolgen, eine trie könnte eine bessere Datenstruktur als eine Karte oder eine hash_map. Ich sagte "könnte", obwohl - eine hash_map schon nur durchquert Sie die Taste einmal pro Suche, sollte also ziemlich schnell. Ich will nicht diskutieren es weiter, da andere bereits haben.
Könnte man auch überlegen, ein splay-Baum, wenn einige Tasten sind häufiger sah als die anderen, aber natürlich macht dies das worst-case-lookup schlimmer als eine ausgewogene Struktur und lookups sind mutierenden Operationen, die möglicherweise für Sie wichtig, wenn Sie beispielsweise mit einer reader-writer lock.
Wenn Sie sich sorgen über die Leistung des lookups mehr als änderungen, die Sie machen könnten, besser mit einem AVL-Baum als rot-schwarz, die ich denke ist, was STL-Implementierungen verwenden in der Regel für die Karte. Ein AVL-Baum ist in der Regel besser ausgeglichen und so im Durchschnitt benötigen weniger Vergleiche pro Suche, aber der Unterschied ist marginal.
Suche nach einer Umsetzung dieser, dass du glücklich bist mit vielleicht ein Thema. Eine Suche auf den Boost-Haupt-Seite schlägt vor, dass Sie einen Winkelbereich und AVL-Baum, nicht aber ein trie.
Sie erwähnt in einem Kommentar, dass Sie nie eine Suche, die nicht finden nichts. So könnte man in der Theorie überspringen der Letzte Vergleich, der in einem Baum von 15 < 2^4 Elemente geben könnte, die Sie so etwas wie eine 20-25% speedup, ohne sonst etwas zu tun. In der Tat, vielleicht mehr als das, denn gleich Saiten sind die langsamsten zu vergleichen. Ob es sich lohnt, schreiben Sie Ihre eigenen container, die nur für diese Optimierung ist eine andere Frage.
Könnte Sie auch interessieren die Lokalität der Verweis - ich weiß nicht, ob Sie könnte zu vermeiden, die gelegentliche Seite verpassen, durch die Zuweisung der Tasten und der Knoten, der aus einem kleinen Haufen. Wenn Sie nur etwa 15 Einträge an, dann unter der Annahme einer Dateinamen-Grenze unter 256 bytes, die Sie sicherstellen konnten, dass alles Zugriff innerhalb eines lookup passt in einen einzigen 4k-Seite (neben dem Schlüssel nachgeschlagen wird, natürlich). Kann es sein, dass der Vergleich der strings ist unbedeutend im Vergleich mit ein paar die Seite geladen ist. Allerdings, wenn dies ist Ihr Engpass muss es eine enorme Anzahl von verweisen, also würde ich vermuten, dass alles ziemlich nahe an der CPU. Lohnt vielleicht.
Einen anderen Gedanken: wenn Sie pessimistische sperren auf eine Struktur, wo es eine Menge Streit (Sie sagte in einem Kommentar der Programm Massiv auf multi-threaded) dann unabhängig davon, was der profiler sagt Sie (was-code der CPU-Zyklen werden ausgegeben), könnte es kostet Sie mehr als Sie denken, durch effektiv begrenzen Sie 1 Kern. Versuchen Sie eine reader-writer-lock?
hash_map
ist nicht standard, versuchen Sie es mitunordered_map
erhältlich in tr1 (welcher in steigern, wenn Sie Ihre tool-Kette nicht bereits haben).Einer kleinen Anzahl von Zeichenfolgen, die Sie vielleicht besser mit
vector
alsmap
wird normalerweise implementiert, wie ein Baum.Warum verwenden nicht Sie eine Hashtabelle statt? boost::unordered_map tun könnte. Oder Sie können roll-out Ihre eigene Lösung, und speichern Sie die crc des Strings anstelle des Strings selbst. Oder noch besser, setzen Sie #defines für die Saiten, und verwenden Sie diese für die Suche, z.B.,