是不是真的有什么不同?
让我们从分析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内联方法调用的几率,减少了方法调用开销并进一步提高了性能。
仅在链接方法调用时,这样可以提高冷启动的性能。
JVM不应该优化它吗?
有人可能会争辩说,一旦虚拟机升温,JRE应该能够优化这些指令。但是,这种声明需要支持,并且仍然只适用于长时间运行的流程。
因此,让我们来检查一下这个说法,即使在热身之后也要验证性能。让我们用江铃控股进行基准测试此行为:
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。
它甚至有关系吗?
嗯,这取决于。这当然是一个微观优化。如果一个系统使用Bubble Sort,肯定会有比这更紧迫的性能问题。并非所有的程序都有相同的要求,因此并不都需要遵循相同的规则。
此PMD规则可能仅对重视性能的特定项目有意义,并且将尽一切努力来削减几个ms。这些项目通常会使用几个不同的分析器,微型基准和其他工具。而像PMD这样的工具能够持续关注特定模式,肯定会对他们有所帮助。
PMD有许多其他规则可用,这可能适用于许多其他项目。仅仅因为这个特定的规则可能不适用于你的项目并不意味着这个工具没有用处,只需花时间审查可用的规则并选择那些对你真正重要的规则。
希望能够为每个人清除它。
尝试使用'javap'反编译这两种样式以查看区别。虽然字节码存在差异,但这感觉就像JIT可能为您处理的不必要的微观优化。 –
@AndyTurner的差异将被HotSpot优化;您在运行时不会看到任何性能差异 – yole
总是如此担心这些新程序员的表现。 – Kayaman