Hallo, wann ist es eigentlich notwendig String-Konkatenierungen "manuell" mit StringBuilder/StringBuffer zu optimieren, wenn man mit einem Java 6 Compiler arbeitet?
Folgender Code wird scheinbar von javac automatisch zu einer Konkatenierung mittels StringBuilder optimiert, wie man z.B. im generierten Byte-Code sehen kann:
Java:
classTest{publicstaticvoidmain(String[] args){String s ="abc";// ...
s +="def";}}
Wann genau kann ich mich auf diese automatische Optimierung verlassen und wann sollte ich sie (an performace-kritischen Stellen) selber ausprogrammieren?
StringBuffer immer dann verwenden, wenn oft concateniert wird, z.B.: in einer Schleife usw.
Wenn ein String einmal initialisiert wird und sich dann nicht mehr ändert, dann kannst du problemlos "String" verwenden.
Folgender Code wird scheinbar von javac automatisch zu einer Konkatenierung mittels StringBuilder optimiert, wie man z.B. im generierten Byte-Code sehen kann
Die Stringkonkatenierung mit + wird immer (wenn nötig) vom Compiler zu einer Konkatenierung mit StringBuilder umgesetzt. Fügt man so Compile-Time-Konstanten zusammen, ist ein Strringbuilder überflüssig und es wird gleich ein fertiger String im Bytecode erzeugt.
Wann genau kann ich mich auf diese automatische Optimierung verlassen und wann sollte ich sie (an performace-kritischen Stellen) selber ausprogrammieren?
optimiert werden kann meiner Ansicht nach eine einzelne Zeile
st += a + b+ c+ d+ e+ f;
bestenfalls mehrere String-Zeilen direkt hintereinander
st += a + b;
st += c+ d;
(auch schon fraglich was bei Exceptions usw. passiert)
unmöglich kann ich mir das für den Paradefall einer Schleife vorstellen:
Java:
for(int i=0; i<10; i++){
st +=", "+i;}
hier vor der Schleife einen Builder einzufügen, nach der Schleife den String zu erstellen, das wäre schon sehr aufwendig,
besonders wenn noch anderer Code in der Schleife steht
dass die Zeile für sich optimiert wird ist klar aber eben nicht das entscheidene,
sondern dass 10x ein StringBuilder und jeweils ein String erzeugt wird, in jedem Schleifendurchlauf
richtig optiomal braucht man nur EINEN StringBuilder
Spaßeshalber habe ich mal meinen QuickBench darauf losgelassen:
Java:
publicclassStringBenches{publicstaticvoidmain(String[] args){QuickBench.benchNxM(10,50000,newlong[]{1000},newlong[]{1000},newBench[]{newStringConcatenation_Plus(),newStringConcatenation_Concat(),newStringConcatenation_StringBuffer(),newStringConcatenation_InnerStringBuffer(),newStringConcatenation_StringBuilder(),newStringConcatenation_InnerStringBuilder()});}/* einfaches s += ... */staticclassStringConcatenation_PlusimplementsBench{publicStringConcatenation_Plus(){super();}@OverridepublicStringgetName(){return"String: Plus";}@Overridepublicvoidreset(){}@Overridepublicvoidprepare(long lm){}@Overridepublicvoidexecute(long lm){String s ="";for(int i =0; i < lm; i++){
s += i;}}};/* s = s.concat(...) */staticclassStringConcatenation_ConcatimplementsBench{publicStringConcatenation_Concat(){super();}@OverridepublicStringgetName(){return"String: Concat";}@Overridepublicvoidreset(){}@Overridepublicvoidprepare(long lm){}@Overridepublicvoidexecute(long lm){String s ="";for(int i =0; i < lm; i++){
s = s.concat(String.valueOf(i));}}};/* StringBuffer ausserhalb der Schleife */staticclassStringConcatenation_StringBufferimplementsBench{publicStringConcatenation_StringBuffer(){super();}@OverridepublicStringgetName(){return"String: StringBuffer";}@Overridepublicvoidreset(){}@Overridepublicvoidprepare(long lm){}@Overridepublicvoidexecute(long lm){StringBuffer s =newStringBuffer();for(int i =0; i < lm; i++){
s.append(String.valueOf(i));}}};/* "optimiertes" Beispiel aus dem Thread */staticclassStringConcatenation_InnerStringBufferimplementsBench{publicStringConcatenation_InnerStringBuffer(){super();}@OverridepublicStringgetName(){return"String: In-StringBuffer";}@Overridepublicvoidreset(){}@Overridepublicvoidprepare(long lm){}@Overridepublicvoidexecute(long lm){String s ="";for(int i =0; i < lm; i++){
s =newStringBuffer(String.valueOf(s)).append(String.valueOf(i)).toString();}}};/* StringBuilder ausserhalb der Schleife */staticclassStringConcatenation_StringBuilderimplementsBench{publicStringConcatenation_StringBuilder(){super();}@OverridepublicStringgetName(){return"String: StringBuilder";}@Overridepublicvoidreset(){}@Overridepublicvoidprepare(long lm){}@Overridepublicvoidexecute(long lm){StringBuilder s =newStringBuilder();for(int i =0; i < lm; i++){
s.append(String.valueOf(i));}}};/* "optimiertes" Beispiel aus dem Thread */staticclassStringConcatenation_InnerStringBuilderimplementsBench{publicStringConcatenation_InnerStringBuilder(){super();}@OverridepublicStringgetName(){return"String: In-StringBuilder";}@Overridepublicvoidreset(){}@Overridepublicvoidprepare(long lm){}@Overridepublicvoidexecute(long lm){String s ="";for(int i =0; i < lm; i++){
s =newStringBuilder(String.valueOf(s)).append(String.valueOf(i)).toString();}}};}
Es werden jeweils 1000 mal die Zahlen von 0 bis 999 angehängt.
String: Plus *** ein einfaches konkatenieren durch +=
String: Concat *** ein konkatenieren durch die concat()-Methode
String: StringBuffer *** ein konkatenieren mittels EINEM StringBuffer und der append()-Methode
String: In-StringBuffer *** ein konkatenieren mittels mehreren StringBuffer und der append()-Methode (wie aus dem Beispiel)
String: StringBuilder *** ein konkatenieren mittels EINEM StringBuilder und der append()-Methode
String: In-StringBuilder *** ein konkatenieren mittels mehreren StringBuildern und der append()-Methode (wie aus dem Beispiel)
Summa summarum, wer hätte sich das denken können? Die Fassungen mit einem StringBuffer und einem StringBuilder sind natürlich die "schnellsten". Die Zahlen sind aber allerdings mit Vorsicht zu genießen,wegen Micro-Benchmarking und so weiter... Ich würde jetzt auch nicht raten, wenige Strings, wie in dem Beispiel zum Anfang, jetzt immer mit einem StringBuilder zu verbinden, es kommt halt auf den Fall an.
Nur zeigt das Dekompilat halt aber auch, das die automatischen Optimierungen des Kompilers nicht immer der bessere Weg sein müssen.
Tipp zum Code:
selbst wenn Bench ein nötiges Interface ist, kann man immer noch eine Adapter-Klasse mit leeren Methoden erstellen
damit dann die eigentlichen Tests nicht diese ständig unnötig wiederholen müssen
ein Konstruktor, der nur super() aufruft, ist auch fraglich,
und wo ich grad dabei bei:
getName() könnte im Adapter einfach den Klassenname zurückgeben bisschen lang allerdings