我在Java中有以下代码。
String foo = " ";
方法1:
StringBuffer buf = new StringBuffer();
buf.append("Hello");
buf.append(foo);
buf.append("World");
方法2:
StringBuffer buf = new StringBuffer();
buf.append("Hello").append(foo).append("World");
有人可以启发我,方法2如何提高代码的性能?
https://pmd.github.io/pmd-5.4.2/pmd-java/rules/java/strings.html#ConsecutiveAppendsShouldReuse
答案 0 :(得分:7)
让我们从分析javac输出开始。鉴于代码:
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();
}
}
我们使用javac进行编译,并使用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
如图所示,appendPerLine
变量产生了一个更大的字节码,通过生成几个额外的aload_1
和pop
指令基本上相互抵消(将字符串构建器/缓冲区留在其中)堆叠,并将其删除以丢弃它)。反过来,这意味着JRE将产生更大的呼叫站点并且具有更大的开销。相反,较小的调用点可以提高JVM内联方法调用的几率,减少方法调用开销并进一步提高性能。
仅在链接方法调用时,这可以从冷启动中提高性能。
有人可能会争辩说,一旦VM预热,JRE应该能够优化这些指令。但是,此声明需要支持,并且仍然只适用于长时间运行的进程。
因此,让我们检查一下这个说法,并在热身后验证效果。让我们使用JMH来衡量这种行为:
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();
}
}
我们编译并运行它,我们获得:
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
因此,即使在预热之后,遵循该规则也会使吞吐量提高约4倍。所有这些运行都是使用Oracle JRE 8u121完成的。
当然,你不必相信我,others have done similar analysis你甚至可以try it yourself。
嗯,这取决于。这当然是一种微观优化。如果系统使用冒泡排序,那么肯定存在比此更紧迫的性能问题。并非所有程序都有相同的要求,因此并非都需要遵循相同的规则。
这个PMD规则可能只对那些重视性能的特定项目有意义,并且可以做任何削减几毫秒的事情。这些项目通常使用几种不同的分析器,微基准测试和其他工具。让PMD等工具密切关注特定模式肯定有助于他们。
PMD还有许多其他规则,可能适用于许多其他项目。仅仅因为此特定规则可能不适用于您的项目并不意味着该工具无用,只需花时间查看可用规则并选择对您真正重要的规则。
希望能为所有人清除它。