Ich habe eine leistungsbezogene Frage zur Verwendung von StringBuilder. In einer sehr langen Schleife manipuliere ich a StringBuilder
und übergebe es an eine andere Methode wie diese:
for (loop condition) {
StringBuilder sb = new StringBuilder();
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
Ist das Instanziieren StringBuilder
bei jedem Schleifenzyklus eine gute Lösung? Und ist es besser, stattdessen ein Löschen aufzurufen, wie im Folgenden?
StringBuilder sb = new StringBuilder();
for (loop condition) {
sb.delete(0, sb.length);
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
java
performance
string
stringbuilder
Pier Luigi
quelle
quelle
In der Philosophie, soliden Code zu schreiben, ist es immer besser, den StringBuilder in die Schleife zu setzen. Auf diese Weise geht es nicht über den Code hinaus, für den es bestimmt ist.
Zweitens besteht die größte Verbesserung in StringBuilder darin, ihm eine Anfangsgröße zu geben, um zu verhindern, dass er größer wird, während die Schleife läuft
quelle
Immer noch schneller:
In der Philosophie des Schreibens von festem Code sollte das Innenleben der Methode vor den Objekten verborgen sein, die die Methode verwenden. Aus Sicht des Systems spielt es also keine Rolle, ob Sie den StringBuilder innerhalb oder außerhalb der Schleife neu deklarieren. Da das Deklarieren außerhalb der Schleife schneller ist und das Lesen des Codes nicht komplizierter wird, sollten Sie das Objekt wiederverwenden, anstatt es erneut zu instanziieren.
Auch wenn der Code komplizierter war und Sie sicher wussten, dass die Objektinstanziierung der Engpass ist, kommentieren Sie ihn.
Drei Läufe mit dieser Antwort:
Drei Läufe mit der anderen Antwort:
Obwohl dies nicht signifikant ist,
StringBuilder
ergibt das Einstellen der anfänglichen Puffergröße eine kleine Verstärkung.quelle
Okay, ich verstehe jetzt, was los ist, und es macht Sinn.
Ich hatte den Eindruck, dass
toString
der Basiswert gerade anchar[]
einen String-Konstruktor übergeben wurde, der keine Kopie erstellt hat. Eine Kopie würde dann bei der nächsten "Schreib" -Operation (zdelete
. B. ) erstellt. Ich glaube , das war der FallStringBuffer
in einigen früheren Version. (Es ist nicht jetzt.) Aber nein -toString
übergibt nur das Array (und den Index und die Länge) an den öffentlichenString
Konstruktor, der eine Kopie erstellt.Im
StringBuilder
Fall "Wiederverwendung " erstellen wir also wirklich eine Kopie der Daten pro Zeichenfolge, wobei wir die ganze Zeit über dasselbe char-Array im Puffer verwenden. Wenn SieStringBuilder
jedes Mal einen neuen Puffer erstellen, wird natürlich ein neuer zugrunde liegender Puffer erstellt - und dieser Puffer wird dann kopiert (in unserem speziellen Fall etwas sinnlos, aber aus Sicherheitsgründen), wenn Sie einen neuen String erstellen.All dies führt dazu, dass die zweite Version definitiv effizienter ist - aber gleichzeitig würde ich immer noch sagen, dass es hässlicherer Code ist.
quelle
Da ich glaube, dass aufgrund von im Sun Java-Compiler integrierten Optimierungen, der automatisch StringBuilder (StringBuffers vor J2SE 5.0) erstellt, wenn String-Verkettungen angezeigt werden, noch nicht darauf hingewiesen wurde, entspricht das erste Beispiel in der Frage:
Welches ist besser lesbar, IMO, der bessere Ansatz. Ihre Optimierungsversuche können auf einigen Plattformen zu Gewinnen führen, auf anderen jedoch möglicherweise zu Verlusten.
Wenn Sie jedoch wirklich auf Leistungsprobleme stoßen, sollten Sie diese optimieren. Ich würde jedoch damit beginnen, die Puffergröße des StringBuilder pro Jon Skeet explizit anzugeben.
quelle
Die moderne JVM ist wirklich schlau in solchen Dingen. Ich würde es nicht erraten und etwas Hackiges tun, das weniger wartbar / lesbar ist ... es sei denn, Sie führen richtige Benchmarks mit Produktionsdaten durch, die eine nicht triviale Leistungsverbesserung bestätigen (und dokumentieren;)
quelle
Aufgrund meiner Erfahrung mit der Entwicklung von Software unter Windows würde ich sagen, dass das Löschen des StringBuilder während Ihrer Schleife eine bessere Leistung bietet als das Instanziieren eines StringBuilder bei jeder Iteration. Durch das Löschen wird der Speicher sofort überschrieben, ohne dass eine zusätzliche Zuordnung erforderlich ist. Ich bin mit dem Java-Garbage-Collector nicht vertraut genug, aber ich würde denken, dass das Freigeben und keine Neuzuweisung (es sei denn, Ihr nächster String vergrößert den StringBuilder) vorteilhafter ist als die Instanziierung.
(Meine Meinung widerspricht dem, was alle anderen vorschlagen. Hmm. Zeit, es zu bewerten.)
quelle
Der Grund, warum das Ausführen von 'setLength' oder 'delete' die Leistung verbessert, liegt hauptsächlich darin, dass der Code die richtige Größe des Puffers 'lernt' und weniger für die Speicherzuweisung. Im Allgemeinen empfehle ich, den Compiler die Zeichenfolgenoptimierungen durchführen zu lassen . Wenn die Leistung jedoch kritisch ist, berechne ich häufig die erwartete Größe des Puffers vor. Die Standardgröße für StringBuilder beträgt 16 Zeichen. Wenn Sie darüber hinaus wachsen, muss die Größe geändert werden. Beim Ändern der Größe geht die Leistung verloren. Hier ist ein weiterer Mini-Benchmark, der dies veranschaulicht:
Die Ergebnisse zeigen, dass die Wiederverwendung des Objekts etwa 10% schneller ist als die Erstellung eines Puffers mit der erwarteten Größe.
quelle
LOL, zum ersten Mal habe ich Leute gesehen, die die Leistung durch Kombinieren von Strings in StringBuilder verglichen haben. Wenn Sie zu diesem Zweck "+" verwenden, kann es sogar noch schneller sein; D. Der Zweck der Verwendung von StringBuilder, um das Abrufen des gesamten Strings als Konzept der "Lokalität" zu beschleunigen.
In dem Szenario, dass Sie häufig einen String-Wert abrufen, der nicht häufig geändert werden muss, ermöglicht Stringbuilder eine höhere Leistung beim Abrufen von Strings. Und das ist der Zweck der Verwendung von Stringbuilder. Bitte testen Sie nicht den Hauptzweck von Stringbuilder.
Einige Leute sagten, Flugzeug fliegt schneller. Deshalb habe ich es mit meinem Fahrrad getestet und festgestellt, dass sich das Flugzeug langsamer bewegt. Wissen Sie, wie ich die Experimenteinstellungen einstelle? D.
quelle
Nicht wesentlich schneller, aber aus meinen Tests geht hervor, dass es mit 1.6.0_45 64 Bit im Durchschnitt ein paar Millis schneller ist: Verwenden Sie StringBuilder.setLength (0) anstelle von StringBuilder.delete ():
quelle
Der schnellste Weg ist die Verwendung von "setLength". Der Kopiervorgang ist nicht erforderlich. Die Möglichkeit, einen neuen StringBuilder zu erstellen, sollte vollständig ausfallen . Die langsame Funktion für StringBuilder.delete (int start, int end) ist, weil das Array für den Größenänderungsteil erneut kopiert wird.
Danach aktualisiert StringBuilder.delete () die Datei StringBuilder.count auf die neue Größe. Während StringBuilder.setLength () nur vereinfacht, aktualisieren Sie StringBuilder.count auf die neue Größe.
quelle
Das erste ist besser für den Menschen. Wenn die zweite Version bei einigen Versionen einiger JVMs etwas schneller ist, was dann?
Wenn die Leistung so kritisch ist, umgehen Sie StringBuilder und schreiben Sie Ihren eigenen. Wenn Sie ein guter Programmierer sind und berücksichtigen, wie Ihre App diese Funktion verwendet, sollten Sie sie noch schneller machen können. Lohnend? Wahrscheinlich nicht.
Warum wird diese Frage als "Lieblingsfrage" angesehen? Weil Leistungsoptimierung so viel Spaß macht, egal ob praktisch oder nicht.
quelle
Ich denke nicht, dass es Sinn macht, zu versuchen, die Leistung so zu optimieren. Heute (2019) laufen beide Statements ungefähr 11 Sekunden für 100.000.000 Schleifen auf meinem I5-Laptop:
==> 11000 ms (Deklaration innerhalb der Schleife) und 8236 ms (Deklaration außerhalb der Schleife)
Selbst wenn ich Programme für die Adressveröffentlichung mit einigen Milliarden Schleifen ausführe, beträgt die Differenz 2 Sekunden. Für 100 Millionen Schleifen macht das keinen Unterschied, da diese Programme stundenlang laufen. Beachten Sie auch, dass die Dinge anders sind, wenn Sie nur eine Append-Anweisung haben:
==> 3416 ms (innere Schleife), 3555 ms (äußere Schleife) Die erste Anweisung, die den StringBuilder innerhalb der Schleife erstellt, ist in diesem Fall schneller. Und wenn Sie die Ausführungsreihenfolge ändern, geht es viel schneller:
==> 3638 ms (äußere Schleife), 2908 ms (innere Schleife)
Grüße, Ulrich
quelle
Einmal deklarieren und jedes Mal zuweisen. Es ist ein pragmatischeres und wiederverwendbares Konzept als eine Optimierung.
quelle