为什么StringBuilder链接模式sb.append(x).append(y)比普通的sb.append(x); sb.append(y)更快?

48

我有一个微基准测试,结果非常奇怪:

@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS, batchSize = 1000)
@Measurement(iterations = 40, time = 1, timeUnit = TimeUnit.SECONDS, batchSize = 1000)
public class Chaining {

    private String a1 = "111111111111111111111111";
    private String a2 = "222222222222222222222222";
    private String a3 = "333333333333333333333333";

    @Benchmark
    public String typicalChaining() {
        return new StringBuilder().append(a1).append(a2).append(a3).toString();
    }

    @Benchmark
    public String noChaining() {
        StringBuilder sb = new StringBuilder();
        sb.append(a1);
        sb.append(a2);
        sb.append(a3);
        return sb.toString();
    }
}

我期望两个测试结果是相同的,或者至少非常接近。然而,它们之间的差异几乎达到5倍:

# Run complete. Total time: 00:01:41

Benchmark                  Mode  Cnt      Score     Error  Units
Chaining.noChaining       thrpt   40   8538.236 ± 209.924  ops/s
Chaining.typicalChaining  thrpt   40  36729.523 ± 988.936  ops/s

有人知道这是如何可能的吗?


1
也许它有一个优化规则,用于链接的 .append 调用,在其中一次性构建整个字符串?这将非常合理。 - user3079266
@Mints97 编译器对于常规的 sb.append() 结构不应该做同样的事情吗? - Dmitriy Dumanskiy
3
@JarrodRoberson,我的期望完全正确。你不认为应该一样吗?这几乎是相同的代码。即使是链接产生的字节码也较少。五倍的差距?不可能。 - Dmitriy Dumanskiy
有趣的是,如果你在没有JIT的情况下运行这个程序,它们会按照你的期望工作(顺便说一下,使用-Djava.compiler=NONE参数)。每个基准测试的结果都接近相同(并且速度要慢得多)。 - Todd
5
我的期望是完全正确的,但结果并非你所期望的。你和我对于“完全正确”的定义不同。 - Lew Bloch
显示剩余6条评论
1个回答

59

Java程序中经常出现字符串拼接的模式,例如 a + b + c,因此HotSpot JVM针对这种情况进行了特殊优化:默认开启 -XX:+OptimizeStringConcat

HotSpot JVM在字节码中识别new StringBuilder().append()...append().toString()模式,并将其转换为优化的机器码,而不调用实际的Java方法或分配中间对象。这是一种复合JVM内部函数。

这里是该优化的源代码

另一方面,sb.append(); sb.append(); ...则没有特殊处理,这个序列编译时就像一个普通的Java方法调用一样。

如果使用 -XX:-OptimizeStringConcat 参数重新运行基准测试,则两个变量的性能将相同。


在非字符串构建器类中链接方法是否比独立调用方法更有效率? - Eric
1
@Eric 不,这个优化是针对StringBuilder特定的。 - apangin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接