Почему шаблон привязки StringBuilder sb.append(x).append(y) быстрее обычного sb.append(x); sb.append(у)?
У меня есть microbenchmark, который показывает очень странные результаты:
@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();
}
}
Я ожидаю, что результаты обоих тестов будут одинаковыми или, по крайней мере, очень близкими. Однако разница составляет почти 5x:
# 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
Конкатенация строк a + b + c
является очень частым шаблоном в Java-программах, поэтому для HotSpot JVM предусмотрена специальная оптимизация: -XX:+OptimizeStringConcat
, которая по умолчанию включена.
JVM HotSpot распознает шаблон new StringBuilder().append()...append().toString()
в байтовом коде и переводит его в оптимизированный машинный код без вызова реальных методов Java и без выделения промежуточных объектов. То есть это своего рода составная JVM внутренняя.
Вот этот исходный код для этой оптимизации.
С другой стороны, sb.append(); sb.append(); ...
не обрабатывается специально. Эта последовательность компилируется точно так же, как обычные вызовы методов Java.
Если вы повторите тест с -XX:-OptimizeStringConcat
, производительность будет одинаковой для обоих вариантов.