Почему конкатенация строк быстрее, чем String.valueOf для преобразования целого в строку?
У меня есть ориентир:
@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
Это показывает, что объединение пустой строки с целым числом на 30% быстрее, чем вызов String.value(100).
Я понимаю, что "+ 100 преобразован в
new StringBuilder().append(100).toString()
Оптимизация
и -XX:+OptimizeStringConcat
применяется, что делает ее быстрой. Я не понимаю, почему valueOf
сам медленнее, чем конкатенация.
Может кто-то объяснить, что именно происходит, и почему "+ 100 быстрее. Какую магию делает OptimizeStringConcat
?
Ответы
Ответ 1
Как вы уже упоминали, JSM HotSpot имеет оптимизацию -XX:+OptimizeStringConcat
, которая распознает шаблон StringBuilder и заменяет его высоконаправленным рукописным IR-графиком, тогда как String.valueOf()
полагается на общие оптимизации компилятора.
Я обнаружил следующие ключевые отличия, проанализировав сгенерированный код сборки:
- Оптимизированный concat не создает нулевой массив
char[]
для строки результата, а массив, созданный Integer.toString
, очищается после выделения, как и любой другой обычный объект.
- Оптимизированный concat переводит цифры в символы простым добавлением константы '0', а
Integer.getChars
использует поиск в таблице с соответствующей оценкой границ массива и т.д.
Существуют и другие незначительные отличия в реализации PhaseStringOpts::int_getChars
против Integer.getChars
, но Я думаю, они не так важны для производительности.
Кстати, если вы берете большее число (например, 1234567890), разница в производительности будет незначительной из-за дополнительного цикла в Integer.getChars
, которая преобразует две цифры в один раз.