2016-06-07 18 views
2

Ich habe das folgende Stück Code in Java.Wie kann ich die Leistung verbessern, wenn append() StringBuffer (oder StringBuilder) nacheinander aufgerufen wird, ohne die Zielvariable wiederzuverwenden

String foo = " "; 

Methode 1:

StringBuffer buf = new StringBuffer(); 
buf.append("Hello"); 
buf.append(foo); 
buf.append("World"); 

Methode 2:

StringBuffer buf = new StringBuffer(); 
buf.append("Hello").append(foo).append("World"); 

Kann jemand mich aufklären, wie Methode 2 kann die Leistung des Codes verbessern?

https://pmd.github.io/pmd-5.4.2/pmd-java/rules/java/strings.html#ConsecutiveAppendsShouldReuse

+6

Versuchen Sie, die beiden Stile mit 'javap' zu dekompilieren, um den Unterschied zu sehen. Obwohl es einen Unterschied im Byte-Code gibt, fühlt sich dies wie unnötige Mikro-Optimierung an, die der JIT wahrscheinlich für Sie erledigen würde. –

+0

@AndyTurner der Unterschied wird von HotSpot optimiert werden; Sie sehen keinen Leistungsunterschied zur Laufzeit – yole

+5

Immer so besorgt über die Leistung dieser neuen Programmierer. – Kayaman

Antwort

5

Ist es wirklich anders?

Beginnen wir mit der Analyse der Java-Ausgabe. In Anbetracht der Code:

public class Main { 
    public String appendInline() { 
    final StringBuilder sb = new StringBuilder().append("some").append(' ').append("string"); 
    return sb.toString(); 
    } 

    public String appendPerLine() { 
    final StringBuilder sb = new StringBuilder(); 
    sb.append("some"); 
    sb.append(' '); 
    sb.append("string"); 
    return sb.toString(); 
    } 
} 

Wir erstellen mit javac, und überprüfen Sie die Ausgabe mit javap -c -s

public java.lang.String appendInline(); 
    descriptor:()Ljava/lang/String; 
    Code: 
     0: new   #2     // class java/lang/StringBuilder 
     3: dup 
     4: invokespecial #3     // Method java/lang/StringBuilder."<init>":()V 
     7: ldc   #4     // String some 
     9: invokevirtual #5     // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
     12: bipush  32 
     14: invokevirtual #6     // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder; 
     17: ldc   #7     // String string 
     19: invokevirtual #5     // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
     22: astore_1 
     23: aload_1 
     24: invokevirtual #8     // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
     27: areturn 

    public java.lang.String appendPerLine(); 
    descriptor:()Ljava/lang/String; 
    Code: 
     0: new   #2     // class java/lang/StringBuilder 
     3: dup 
     4: invokespecial #3     // Method java/lang/StringBuilder."<init>":()V 
     7: astore_1 
     8: aload_1 
     9: ldc   #4     // String some 
     11: invokevirtual #5     // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
     14: pop 
     15: aload_1 
     16: bipush  32 
     18: invokevirtual #6     // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder; 
     21: pop 
     22: aload_1 
     23: ldc   #7     // String string 
     25: invokevirtual #5     // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
     28: pop 
     29: aload_1 
     30: invokevirtual #8     // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
     33: areturn 

Wie zu sehen ist, die appendPerLine Variante erzeugt eine viel größere Bytecode, durch mehrere zusätzliche aload_1 und pop Anweisungen erzeugen, die Im Grunde heben sie sich gegenseitig auf (indem sie den String Builder/Puffer im Stapel lassen und ihn entfernen, um ihn zu verwerfen). Dies bedeutet wiederum, dass die JRE eine größere Callsite produziert und einen höheren Overhead hat. Im Gegensatz dazu verbessert eine kleinere Callsite die Wahrscheinlichkeit, dass die JVM die Methodenaufrufe inline führt, wodurch der Aufwand für Methodenaufrufe verringert und die Leistung weiter verbessert wird.

Dies allein verbessert die Leistung von einem Kaltstart beim Verketten von Methodenaufrufen.

Sollte die JVM diese nicht optimieren?

Man könnte argumentieren, dass die JRE in der Lage sein sollte, diese Anweisungen zu optimieren, sobald die VM aufgewärmt ist. Dieser Anspruch muss jedoch unterstützt werden und gilt nur für lang andauernde Prozesse.

Lassen Sie uns diesen Anspruch überprüfen und die Leistung auch nach dem Aufwärmen überprüfen. Lassen Sie uns JMH nutzen dieses Verhalten Benchmark:

import org.openjdk.jmh.annotations.Benchmark; 
import org.openjdk.jmh.annotations.Param; 
import org.openjdk.jmh.annotations.Scope; 
import org.openjdk.jmh.annotations.State; 

@State(Scope.Benchmark) 
public class StringBenchmark { 
    private String from = "Alex"; 
    private String to = "Readers"; 
    private String subject = "Benchmarking with JMH"; 

    @Param({"16"}) 
    private int size; 

    @Benchmark 
    public String testEmailBuilderSimple() { 
     StringBuilder builder = new StringBuilder(size); 
     builder.append("From"); 
     builder.append(from); 
     builder.append("To"); 
     builder.append(to); 
     builder.append("Subject"); 
     builder.append(subject); 
     return builder.toString(); 
    } 

    @Benchmark 
    public String testEmailBufferSimple() { 
     StringBuffer buffer = new StringBuffer(size); 
     buffer.append("From"); 
     buffer.append(from); 
     buffer.append("To"); 
     buffer.append(to); 
     buffer.append("Subject"); 
     buffer.append(subject); 
     return buffer.toString(); 
    } 

    @Benchmark 
    public String testEmailBuilderChain() { 
     return new StringBuilder(size).append("From").append(from).append("To").append(to).append("Subject") 
       .append(subject).toString(); 
    } 

    @Benchmark 
    public String testEmailBufferChain() { 
     return new StringBuffer(size).append("From").append(from).append("To").append(to).append("Subject") 
       .append(subject).toString(); 
    } 
} 

Wir kompilieren und ausführen und wir erhalten:

Benchmark        (size) Mode Cnt   Score  Error Units 
StringBenchmark.testEmailBufferChain  16 thrpt 200 22981842.957 ± 238502.907 ops/s 
StringBenchmark.testEmailBufferSimple  16 thrpt 200 5789967.103 ± 62743.660 ops/s 
StringBenchmark.testEmailBuilderChain  16 thrpt 200 22984472.260 ± 212243.175 ops/s 
StringBenchmark.testEmailBuilderSimple  16 thrpt 200 5778824.788 ± 59200.312 ops/s 

Also, auch nach dem Aufwärmen, nach der Regel eine ~ 4X Verbesserung der Durchsatz erzeugt. Alle diese Läufe wurden mit Oracle JRE 8u121 durchgeführt.

Natürlich müssen Sie mir nicht glauben, others have done similar analysis und Sie können sogar try it yourself.

Ist es überhaupt wichtig?

Nun, es kommt darauf an. Dies ist sicherlich eine Mikrooptimierung. Wenn ein System Bubble Sort verwendet, gibt es sicherlich drängendere Leistungsprobleme. Nicht alle Programme haben die gleichen Anforderungen und daher müssen nicht alle die gleichen Regeln befolgen.

Diese PMD-Regel ist wahrscheinlich nur für bestimmte Projekte von Bedeutung, die die Leistung sehr schätzen und alles tun, um ein paar ms zu sparen. Solche Projekte würden normalerweise mehrere verschiedene Profiler, Mikrobenchmarks und andere Werkzeuge verwenden. Und Werkzeuge wie PMD, die bestimmte Muster im Auge behalten, werden ihnen sicherlich helfen.

PMD hat viele andere Regeln, die wahrscheinlich für viele andere Projekte gelten. Nur weil diese spezielle Regel möglicherweise nicht auf Ihr Projekt zutrifft, bedeutet das nicht, dass das Tool nicht nützlich ist. Nehmen Sie sich einfach die Zeit, die verfügbaren Regeln zu überprüfen und diejenigen auszuwählen, die Ihnen wirklich wichtig sind.

Hoffe, dass es für alle aufräumt.