gpt4 book ai didi

java - 如果在 StringBuffer(或 StringBuilder)上连续调用 append() 而不重用目标变量,我如何提高性能

转载 作者:行者123 更新时间:2023-12-01 12:39:36 29 4
gpt4 key购买 nike

我在 Java 中有以下一段代码。

String foo = " ";

方法一:
StringBuffer buf = new StringBuffer();
buf.append("Hello");
buf.append(foo);
buf.append("World");

方法二:
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

最佳答案

真的不一样吗?

让我们从分析 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_1pop基本上相互抵消的指令(将字符串构建器/缓冲区留在堆栈中,并删除它以丢弃它)。反过来,这意味着 JRE 将产生更大的调用站点并具有更大的开销。相反,较小的调用站点会提高 JVM 内联方法调用的机会,从而减少方法调用开销并进一步提高性能。

当链接方法调用时,仅此一项就可以提高冷启动的性能。

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 有许多其他可用的规则,这些规则可能适用于许多其他项目。仅仅因为此特定规则可能不适用于您的项目并不意味着该工具没有用,只需花时间查看可用规则并选择对您真正重要的规则。

希望能为大家解惑。

关于java - 如果在 StringBuffer(或 StringBuilder)上连续调用 append() 而不重用目标变量,我如何提高性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37672785/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com