Wir müssen ständig Strings für die Protokollausgabe erstellen und so weiter. In den JDK-Versionen haben wir gelernt, wann StringBuffer
(viele Anhänge, thread-sicher) und StringBuilder
(viele Anhänge, nicht thread-sicher) zu verwenden sind.
Was ist der Rat zur Verwendung String.format()
? Ist es effizient oder müssen wir uns an die Verkettung von Einzeilern halten, bei denen Leistung wichtig ist?
zB hässlicher alter Stil,
String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";
ordentlicher neuer Stil (String.format, der möglicherweise langsamer ist),
String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);
Hinweis: Mein spezieller Anwendungsfall sind die Hunderte von Einzeiler-Protokollzeichenfolgen in meinem Code. Sie beinhalten keine Schleife, sind also StringBuilder
zu schwer. Ich interessiere mich String.format()
speziell für.
Antworten:
Ich habe eine kleine Klasse zum Testen geschrieben, die die bessere Leistung der beiden hat und + dem Format voraus ist. um den Faktor 5 bis 6. Probieren Sie es selbst aus
Das Ausführen der obigen Schritte für verschiedene N zeigt, dass sich beide linear verhalten, jedoch
String.format
5 bis 30 Mal langsamer sind.Der Grund ist, dass in der aktuellen Implementierung
String.format
zuerst die Eingabe mit regulären Ausdrücken analysiert und dann die Parameter ausgefüllt werden. Die Verkettung mit Plus hingegen wird von javac (nicht von der JIT) optimiert undStringBuilder.append
direkt verwendet.quelle
Ich nahm Hhafez- Code und fügte einen Gedächtnistest hinzu :
Ich führe dies separat für jeden Ansatz aus, den Operator '+', String.format und StringBuilder (Aufruf von toString ()), damit der verwendete Speicher nicht von anderen Ansätzen beeinflusst wird. Ich habe weitere Verkettungen hinzugefügt und die Zeichenfolge als "Blah" + i + "Blah" + i + "Blah" + i + "Blah" erstellt.
Das Ergebnis ist wie folgt (durchschnittlich jeweils 5 Läufe):
Annäherungszeit (ms)
Zugeordneter Speicher (lang) '+' Operator 747 320.504
String.format 16484 373.312
StringBuilder 769 57.344
Wir können sehen, dass String '+' und StringBuilder zeitlich praktisch identisch sind, aber StringBuilder ist bei der Speichernutzung viel effizienter. Dies ist sehr wichtig, wenn wir viele Protokollaufrufe (oder andere Anweisungen mit Zeichenfolgen) in einem Zeitintervall haben, das kurz genug ist, damit der Garbage Collector die vielen Zeichenfolgeninstanzen, die sich aus dem Operator '+' ergeben, nicht bereinigen kann.
Und eine Notiz, BTW, vergessen Sie nicht , die Protokollierung zu überprüfen Ebene , bevor die Nachricht zu konstruieren.
Schlussfolgerungen:
quelle
+
Operator wird der entsprechendeStringBuilder
Code kompiliert . Mikrobenchmarks wie dieses sind keine gute Methode zur Messung der Leistung - warum nicht jvisualvm verwenden, es ist aus einem bestimmten Grund im JDK.String.format()
wird langsamer sein, aber aufgrund der Zeit zum Analysieren der Formatzeichenfolge und nicht der Objektzuweisungen. Es ist ein guter Rat, die Erstellung von Protokollierungsartefakten zu verschieben, bis Sie sicher sind, dass sie benötigt werden. Wenn sich dies jedoch auf die Leistung auswirkt, ist dies am falschen Ort.And a note, BTW, don't forget to check the logging level before constructing the message.
ist kein guter Rat. Angenommen, es handelt sichjava.util.logging.*
speziell um eine Überprüfung der Protokollierungsstufe, wenn Sie über eine erweiterte Verarbeitung sprechen, die nachteilige Auswirkungen auf ein Programm haben würde, die Sie nicht möchten, wenn für ein Programm die Protokollierung nicht auf der entsprechenden Ebene aktiviert ist. Die Formatierung von Zeichenfolgen ist überhaupt nicht diese Art der Verarbeitung. Die Formatierung ist Teil desjava.util.logging
Frameworks, und der Protokollierer selbst überprüft die Protokollierungsstufe, bevor der Formatierer jemals aufgerufen wird.Alle hier vorgestellten Benchmarks weisen einige Mängel auf , daher sind die Ergebnisse nicht zuverlässig.
Ich war überrascht, dass niemand JMH für das Benchmarking verwendet hat, also habe ich es getan.
Ergebnisse:
Einheiten sind Operationen pro Sekunde, je mehr desto besser. Benchmark-Quellcode . OpenJDK IcedTea 2.5.4 Java Virtual Machine wurde verwendet.
Der alte Stil (mit +) ist also viel schneller.
quelle
Ihr alter hässlicher Stil wird von JAVAC 1.6 automatisch wie folgt kompiliert:
Es gibt also absolut keinen Unterschied zwischen diesem und der Verwendung eines StringBuilder.
String.format ist viel schwerer, da es einen neuen Formatierer erstellt, Ihren Eingabeformat-String analysiert, einen StringBuilder erstellt, alles daran anfügt und toString () aufruft.
quelle
+
und in derStringBuilder
Tat. Leider gibt es in anderen Antworten in diesem Thread viele Fehlinformationen. Ich bin fast versucht, die Frage zu ändernhow should I not be measuring performance
.Das String.format von Java funktioniert folgendermaßen:
Wenn das endgültige Ziel für diese Daten ein Stream ist (z. B. Rendern einer Webseite oder Schreiben in eine Datei), können Sie die Formatblöcke direkt in Ihrem Stream zusammenstellen:
Ich spekuliere, dass der Optimierer die Formatzeichenfolgenverarbeitung optimieren wird. In diesem Fall bleibt Ihnen die gleiche amortisierte Leistung wie beim manuellen Abrollen Ihres String.Formats in einem StringBuilder.
quelle
String.format
in inneren Schleifen (millionenfach ausgeführt) zu mehr als 10% meiner Ausführungszeit führtejava.util.Formatter.parse(String)
. Dies scheint darauf hinzudeuten, dass Sie in inneren Schleifen das AufrufenFormatter.format
oder alles, was es aufruft, vermeiden sollten , einschließlichPrintStream.format
(ein Fehler in Javas Standardbibliothek IMO, insbesondere da Sie die analysierte Formatzeichenfolge nicht zwischenspeichern können).Um die erste Antwort oben zu erweitern / zu korrigieren, ist es keine Übersetzung, bei der String.format tatsächlich helfen würde.
String.format hilft beim Drucken eines Datums / einer Uhrzeit (oder eines numerischen Formats usw.), bei dem es Unterschiede in der Lokalisierung (l10n) gibt (dh einige Länder drucken den 04. Februar 2009 und andere den 04. Februar 2009).
Bei der Übersetzung geht es nur darum, externalisierbare Zeichenfolgen (wie Fehlermeldungen und was nicht) in ein Eigenschaftspaket zu verschieben, damit Sie mit ResourceBundle und MessageFormat das richtige Paket für die richtige Sprache verwenden können.
Wenn man sich all das oben Genannte ansieht, würde ich sagen, dass String.format vs. einfache Verkettung in Bezug auf die Leistung auf das hinausläuft, was Sie bevorzugen. Wenn Sie es vorziehen, Aufrufe von .format gegenüber Verkettung zu betrachten, sollten Sie dies auf jeden Fall tun.
Schließlich wird Code viel mehr gelesen als geschrieben.
quelle
In Ihrem Beispiel ist das Leistungsproblem nicht allzu unterschiedlich, es sind jedoch noch andere Aspekte zu berücksichtigen: die Speicherfragmentierung. Selbst bei einer Verkettungsoperation wird eine neue Zeichenfolge erstellt, auch wenn diese temporär ist (es dauert einige Zeit, sie zu analysieren, und es ist mehr Arbeit). String.format () ist nur besser lesbar und erfordert weniger Fragmentierung.
Wenn Sie häufig ein bestimmtes Format verwenden, vergessen Sie nicht, dass Sie die Formatter () -Klasse direkt verwenden können (String.format () instanziiert lediglich eine einmal verwendete Formatter-Instanz).
Außerdem sollten Sie noch etwas beachten: Achten Sie auf die Verwendung von Teilzeichenfolgen (). Beispielsweise:
Diese große Zeichenfolge befindet sich noch im Speicher, da Java-Teilzeichenfolgen genau so funktionieren. Eine bessere Version ist:
oder
Die zweite Form ist wahrscheinlich nützlicher, wenn Sie gleichzeitig andere Dinge tun.
quelle
Im Allgemeinen sollten Sie String.Format verwenden, da es relativ schnell ist und die Globalisierung unterstützt (vorausgesetzt, Sie versuchen tatsächlich, etwas zu schreiben, das vom Benutzer gelesen wird). Dies erleichtert auch die Globalisierung, wenn Sie versuchen, eine Zeichenfolge gegenüber 3 oder mehr pro Anweisung zu übersetzen (insbesondere für Sprachen mit drastisch unterschiedlichen grammatikalischen Strukturen).
Wenn Sie nie vorhaben, etwas zu übersetzen, verlassen Sie sich entweder auf Javas integrierte Konvertierung von + -Operatoren in
StringBuilder
. Oder verwenden Sie JavaStringBuilder
explizit.quelle
Eine andere Perspektive nur aus Sicht der Protokollierung.
Ich sehe viele Diskussionen im Zusammenhang mit der Anmeldung in diesem Thread, daher dachte ich daran, meine Erfahrung als Antwort hinzuzufügen. Vielleicht findet es jemand nützlich.
Ich denke, die Motivation für die Protokollierung mit Formatierer liegt in der Vermeidung der Verkettung von Zeichenfolgen. Grundsätzlich möchten Sie keinen Overhead von String Concat haben, wenn Sie ihn nicht protokollieren möchten.
Sie müssen nicht wirklich concat / formatieren, es sei denn, Sie möchten sich anmelden. Sagen wir mal, ob ich eine solche Methode definiere
Bei diesem Ansatz wird der Cancat / Formatierer überhaupt nicht aufgerufen, wenn es sich um eine Debug-Nachricht handelt und debugOn = false
Es ist jedoch immer noch besser, hier StringBuilder anstelle des Formatierers zu verwenden. Die Hauptmotivation ist, dies zu vermeiden.
Gleichzeitig mag ich es nicht, seitdem für jede Protokollierungsanweisung einen "if" -Block hinzuzufügen
Daher ziehe ich es vor, eine Protokollierungsdienstprogrammklasse mit Methoden wie oben zu erstellen und sie überall zu verwenden, ohne mir Gedanken über Leistungseinbußen und andere damit verbundene Probleme zu machen.
quelle
Ich habe gerade den Test von hhafez so geändert, dass er StringBuilder enthält. StringBuilder ist 33-mal schneller als String.format mit dem jdk 1.6.0_10-Client unter XP. Durch Verwendung des Schalters -server wird der Faktor auf 20 gesenkt.
Obwohl dies drastisch klingen mag, halte ich es nur in seltenen Fällen für relevant, da die absoluten Zahlen ziemlich niedrig sind: 4 s für 1 Million einfache String.format-Aufrufe sind in Ordnung - solange ich sie für die Protokollierung oder das verwende mögen.
Update: Wie von sjbotha in den Kommentaren hervorgehoben, ist der StringBuilder-Test ungültig, da ihm ein Finale fehlt
.toString()
.Der korrekte Beschleunigungsfaktor von
String.format(.)
bisStringBuilder
ist 23 auf meiner Maschine (16 mit dem-server
Schalter).quelle
Hier ist eine modifizierte Version des Hhafez-Eintrags. Es enthält eine String Builder-Option.
}}
Zeit danach für Schleife 391 Zeit danach für Schleife 4163 Zeit danach für Schleife 227
quelle
Die Antwort darauf hängt stark davon ab, wie Ihr spezifischer Java-Compiler den von ihm generierten Bytecode optimiert. Zeichenfolgen sind unveränderlich und theoretisch kann jede "+" - Operation eine neue erstellen. Ihr Compiler optimiert jedoch mit ziemlicher Sicherheit Zwischenschritte beim Erstellen langer Zeichenfolgen. Es ist durchaus möglich, dass beide obigen Codezeilen genau denselben Bytecode generieren.
Die einzige Möglichkeit, dies zu wissen, besteht darin, den Code iterativ in Ihrer aktuellen Umgebung zu testen. Schreiben Sie eine QD-App, die Zeichenfolgen iterativ in beide Richtungen verkettet, und sehen Sie, wie sie gegeneinander ablaufen.
quelle
Erwägen Sie die Verwendung
"hello".concat( "world!" )
einer kleinen Anzahl von Zeichenfolgen in der Verkettung. Es könnte für die Leistung sogar besser sein als andere Ansätze.Wenn Sie mehr als 3 Zeichenfolgen haben, sollten Sie StringBuilder oder nur String verwenden, je nachdem, welchen Compiler Sie verwenden.
quelle