Ich habe einige Leute gehört, die sich Sorgen über den Operator "+" in std :: string und verschiedene Problemumgehungen gemacht haben, um die Verkettung zu beschleunigen. Sind diese wirklich notwendig? Wenn ja, wie lassen sich Zeichenfolgen in C ++ am besten verketten?
108
libstdc++
tut dies zum Beispiel . Wenn Sie operator + mit temporären Elementen aufrufen, kann dies zu einer nahezu ebenso guten Leistung führen - möglicherweise ein Argument für die Standardeinstellung aus Gründen der Lesbarkeit, es sei denn, es gibt Benchmarks, die belegen, dass es sich um einen Engpass handelt. Eine standardisierte Variableappend()
wäre jedoch sowohl optimal als auch lesbar ...Antworten:
Die zusätzliche Arbeit lohnt sich wahrscheinlich nicht, es sei denn, Sie brauchen wirklich wirklich Effizienz. Sie werden wahrscheinlich eine viel bessere Effizienz erzielen, wenn Sie stattdessen den Operator + = verwenden.
Nach diesem Haftungsausschluss werde ich Ihre eigentliche Frage beantworten ...
Die Effizienz der STL-Zeichenfolgenklasse hängt von der Implementierung der von Ihnen verwendeten STL ab.
Sie könnten Effizienz garantieren und mehr Kontrolle haben indem Sie die Verkettung manuell über die integrierten c-Funktionen durchführen.
Warum Operator + nicht effizient ist:
Schauen Sie sich diese Oberfläche an:
Sie können sehen, dass nach jedem + ein neues Objekt zurückgegeben wird. Das bedeutet, dass jedes Mal ein neuer Puffer verwendet wird. Wenn Sie eine Menge zusätzlicher + Operationen ausführen, ist dies nicht effizient.
Warum Sie es effizienter machen können:
Überlegungen zur Umsetzung:
Seildatenstruktur:
Wenn Sie wirklich schnelle Verkettungen benötigen, sollten Sie eine Seildatenstruktur verwenden .
quelle
Reservieren Sie vorher Ihren endgültigen Speicherplatz und verwenden Sie dann die Append-Methode mit einem Puffer. Angenommen, Sie erwarten eine endgültige Zeichenfolgenlänge von 1 Million Zeichen:
quelle
Ich würde mir darüber keine Sorgen machen. Wenn Sie dies in einer Schleife tun, weisen Zeichenfolgen immer Speicher zu, um Neuzuweisungen zu minimieren. Verwenden Sie dies nur
operator+=
in diesem Fall. Und wenn Sie es manuell tun, so etwas oder längerDann werden temporäre Dateien erstellt - auch wenn der Compiler einige Kopien von Rückgabewerten entfernen könnte. Dies liegt daran, dass in einem nacheinander aufgerufenen
operator+
Objekt nicht bekannt ist, ob der Referenzparameter auf ein benanntes Objekt oder ein von einem Unteraufruf zurückgegebenes temporäres Objekt verweistoperator+
. Ich würde mir lieber keine Sorgen machen, bevor ich nicht zuerst ein Profil erstellt habe. Aber nehmen wir ein Beispiel, um das zu zeigen. Wir führen zuerst Klammern ein, um die Bindung klar zu machen. Ich setze die Argumente direkt nach der Funktionsdeklaration, die der Klarheit halber verwendet wird. Darunter zeige ich, was der resultierende Ausdruck dann ist:In diesem Zusatz
tmp1
wurde nun der erste Aufruf von operator + mit den angezeigten Argumenten zurückgegeben. Wir gehen davon aus, dass der Compiler wirklich clever ist und die Rückgabewertkopie optimiert. Am Ende haben wir also eine neue Zeichenfolge, die die Verkettung vona
und enthält" : "
. Nun passiert dies:Vergleichen Sie das mit Folgendem:
Es verwendet dieselbe Funktion für eine temporäre und eine benannte Zeichenfolge! Der Compiler muss also das Argument in eine neue Zeichenfolge kopieren und an diese anhängen und aus dem Hauptteil von zurückgeben
operator+
. Es kann nicht die Erinnerung an eine temporäre nehmen und daran anhängen. Je größer der Ausdruck ist, desto mehr Kopien von Zeichenfolgen müssen erstellt werden.Next Visual Studio und GCC unterstützen die Verschiebungssemantik von c ++ 1x (ergänzt die Kopiersemantik ) und die rvalue-Referenzen als experimentelle Ergänzung. Auf diese Weise können Sie herausfinden, ob der Parameter auf ein temporäres Element verweist oder nicht. Dies wird solche Ergänzungen erstaunlich schnell machen, da alle oben genannten in einer "Add-Pipeline" ohne Kopien enden werden.
Wenn sich herausstellt, dass es sich um einen Engpass handelt, können Sie dies dennoch tun
Die
append
Aufrufe hängen das Argument an*this
und geben dann einen Verweis auf sich selbst zurück. Dort wird also kein Provisorium kopiert. Alternativoperator+=
kann das verwendet werden, aber Sie benötigen hässliche Klammern, um die Priorität festzulegen.quelle
libstdc++
füroperator+(string const& lhs, string&& rhs)
tutreturn std::move(rhs.insert(0, lhs))
. Wenn dann beide temporär sind, wird es direkt direkt sein,operator+(string&& lhs, string&& rhs)
wennlhs
genügend Kapazität verfügbar istappend()
. Wo ich denke, dass dies langsamer sein kann alsoperator+=
wennlhs
es nicht genügend Kapazität hat, wie es dann zurückfälltrhs.insert(0, lhs)
, was nicht nur den Puffer erweitern und die neuen Inhalte wie hinzufügen mussappend()
, sondern auch entlang des ursprünglichen Inhalts vonrhs
rechts verschoben werden muss .operator+=
ist, dassoperator+
immer noch ein Wert zurückgegeben werden muss, also an denmove()
Operanden, an den er angehängt ist. Trotzdem denke ich, dass dies ein relativ geringer Aufwand ist (Kopieren einiger Zeiger / Größen) im Vergleich zum tiefen Kopieren der gesamten Zeichenfolge, also ist es gut!Für die meisten Anwendungen spielt es einfach keine Rolle. Schreiben Sie einfach Ihren Code, ohne zu wissen, wie genau der Operator + funktioniert, und nehmen Sie die Angelegenheit nur dann selbst in die Hand, wenn dies zu einem offensichtlichen Engpass wird.
quelle
Im Gegensatz zu .NET System.Strings, C ++ 's std :: strings sind wandelbar und können daher durch einfache Verkettung aufgebaut werden genauso schnell wie durch andere Methoden.
quelle
operator+
muss keinen neuen String zurückgeben. Implementierer können einen ihrer geänderten Operanden zurückgeben, wenn dieser Operand als rvalue-Referenz übergeben wurde.libstdc++
tut dies zum Beispiel . Wenn Sie alsooperator+
mit temporären Geräten anrufen , kann dies die gleiche oder fast genauso gute Leistung erzielen - was ein weiteres Argument für die Standardeinstellung sein könnte, es sei denn, es gibt Benchmarks, die belegen, dass es sich um einen Engpass handelt.vielleicht stattdessen std :: stringstream?
Aber ich stimme dem Gefühl zu, dass Sie es wahrscheinlich nur wartbar und verständlich halten und dann profilieren sollten, um zu sehen, ob Sie wirklich Probleme haben.
quelle
In Imperfect C ++ präsentiert Matthew Wilson einen dynamischen Zeichenfolgenverketter, der die Länge der endgültigen Zeichenfolge vorberechnet, um nur eine Zuordnung zu erhalten, bevor alle Teile verkettet werden. Wir können auch einen statischen Verketter implementieren, indem wir mit Ausdrucksvorlagen spielen .
Diese Art von Idee wurde in der STLport std :: string-Implementierung implementiert - die aufgrund dieses präzisen Hacks nicht dem Standard entspricht.
quelle
Glib::ustring::compose()
Von den Glibmm-Bindungen zu GLib wird Folgendes ausgeführt: Schätzt undreserve()
s die endgültige Länge basierend auf der bereitgestellten Formatzeichenfolge und den Varargs, dannappend()
s jede (oder ihre formatierte Ersetzung) in einer Schleife. Ich gehe davon aus, dass dies eine ziemlich übliche Arbeitsweise ist.std::string
operator+
ordnet eine neue Zeichenfolge zu und kopiert jedes Mal die beiden Operandenzeichenfolgen. viele Male wiederholen und es wird teuer, O (n).std::string
append
undoperator+=
auf der anderen Seite stößt die Kapazität um 50% jedes Mal , wenn die Zeichenfolge wachsen muss. Dies reduziert die Anzahl der Speicherzuweisungen und Kopiervorgänge erheblich, O (log n).quelle
operator+
allzu genau, da es lange nach dem Debüt von C ++ 11 geschrieben wurde und Überladungen, bei denen eines oder beide Argumente durch eine rvalue-Referenz übergeben werden, die Zuweisung einer neuen Zeichenfolge insgesamt vermeiden können, indem sie in den vorhandenen Puffer von verkettet werden einer der Operanden (obwohl sie möglicherweise neu zugewiesen werden müssen, wenn die Kapazität nicht ausreicht).Für kleine Saiten spielt es keine Rolle. Wenn Sie große Zeichenfolgen haben, sollten Sie diese besser als Vektor oder in einer anderen Sammlung als Teile speichern. Und passen Sie Ihren Algorithmus an, um mit solchen Daten anstelle der einen großen Zeichenfolge zu arbeiten.
Ich bevorzuge std :: ostringstream für komplexe Verkettung.
quelle
Wie bei den meisten Dingen ist es einfacher, etwas nicht zu tun, als es zu tun.
Wenn Sie große Zeichenfolgen an eine GUI ausgeben möchten, kann es sein, dass alles, was Sie ausgeben, die Zeichenfolgen in Teilen besser verarbeiten kann als eine große Zeichenfolge (z. B. das Verketten von Text in einem Texteditor - normalerweise werden die Zeilen getrennt Strukturen).
Wenn Sie in eine Datei ausgeben möchten, streamen Sie die Daten, anstatt eine große Zeichenfolge zu erstellen und diese auszugeben.
Ich habe nie die Notwendigkeit gefunden, die Verkettung schneller zu machen, wenn ich unnötige Verkettung aus langsamem Code entfernt habe.
quelle
Wahrscheinlich die beste Leistung, wenn Sie Speicherplatz in der resultierenden Zeichenfolge vorab zuweisen (reservieren).
Verwendung:
quelle
Ein einfaches Array von Zeichen, das in einer Klasse gekapselt ist, die die Arraygröße und die Anzahl der zugewiesenen Bytes verfolgt, ist am schnellsten.
Der Trick besteht darin, zu Beginn nur eine große Zuordnung vorzunehmen.
beim
https://github.com/pedro-vicente/table-string
Benchmarks
Für Visual Studio 2015, x86-Debug-Build, wesentliche Verbesserung gegenüber C ++ std :: string.
quelle
std::string
. Sie fragen nicht nach einer alternativen Zeichenfolgenklasse.Sie können dies mit Speicherreservierungen für jedes Element versuchen:
quelle