为什么字符串连接比String.valueOf在将整数转换为字符串时更快?

22

我有一个基准:

@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 StringConcatTest {

    private int aInt;

    @Setup
    public void prepare() {
        aInt = 100;
    }

    @Benchmark
    public String emptyStringInt() {
        return "" + aInt;
    }

    @Benchmark
    public String valueOfInt() {
        return String.valueOf(aInt);
    }

}

这是结果:

Benchmark                                          Mode  Cnt      Score      Error  Units
StringConcatTest.emptyStringInt                   thrpt   40  66045.741 ± 1306.280  ops/s
StringConcatTest.valueOfInt                       thrpt   40  43947.708 ± 1140.078  ops/s

这表明将空字符串与整数拼接比调用String.value(100)快30%。 我理解"" + 100转换为

new StringBuilder().append(100).toString()

应用了-XX:+OptimizeStringConcat优化,使其速度更快。我不明白的是为什么valueOf本身比连接操作更慢。

有人能解释一下发生了什么以及为什么"" + 100更快吗?OptimizeStringConcat做了什么魔法?


1
"" + 100 可能更清晰地被编译器认为是一个常量... - Louis Wasserman
1
其中一个是方法调用。而另外一个,编译器可以按照自己的方式进行编译。 - Louis Wasserman
1
@LouisWasserman 它不是编译为常量。它被编译为 StringBuider() 构造。 - Dmitriy Dumanskiy
1
@LewBloch 你可以在基准测试中看到它。JMH避免了JIT“踢出”。@cricket_007 我检查了valueOf的内部,但它没有回答我的问题。 - Dmitriy Dumanskiy
2
我同意这里似乎不是这种情况。这并不是普遍真实的,也不一定在未来的编译器版本中成立,因为上面我链接的讨论所述。 - Louis Wasserman
显示剩余8条评论
1个回答

15
正如你所提到的,HotSpot JVM有一个名为-XX:+OptimizeStringConcat的优化项,它可以识别StringBuilder模式,并将其替换为高度调整过的手写IR图形,而String.valueOf()则依赖于一般的编译器优化。
通过分析生成的汇编代码,我找到了以下关键差异:
  • 优化的连接不会清除为结果字符串创建的char[]数组,而Integer.toString创建的数组在分配后像任何其他常规对象一样被清除。
  • 优化的连接通过简单的将数字转换为字符加上'0'常量来实现,而Integer.getChars则使用表查找和相关的数组界限检查等。

PhaseStringOpts::int_getCharsInteger.getChars的实现还有其他细微差异,但我想它们对性能影响不大。


顺便说一下,如果你使用更大的数字(例如1234567890),由于Integer.getChars中的额外循环可以一次转换两个数字,因此性能差异将是可以忽略不计的。


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