Как реализована конкатенация строк в Java 9?
Как написано в JEP 280: Указать конкатенацию строк:
Измените статическую последовательность байт-кода String
-concatenation, сгенерированную javac
чтобы использовать invokedynamic
вызовы функций библиотеки JDK. Это позволит в будущем оптимизаций String
конкатенаций, не требуя дополнительные изменения в байткод emmited по javac
.
Здесь я хочу понять, что такое использование вызовов invokedynamic
и чем конкатенация байт-кода отличается от invokedynamic
?
Ответы
Ответ 1
"Старый" способ выводит кучу StringBuilder
-ориентированных операций. Рассмотрим эту программу:
public class Example {
public static void main(String[] args)
{
String result = args[0] + "-" + args[1] + "-" + args[2];
System.out.println(result);
}
}
Если мы скомпилируем это с помощью JDK 8 или ранее, а затем с помощью javap -c Example
, чтобы увидеть байт-код, мы увидим что-то вроде этого:
public class Example {
public Example();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: aload_0
8: iconst_0
9: aaload
10: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
13: ldc #5 // String -
15: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: aload_0
19: iconst_1
20: aaload
21: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: ldc #5 // String -
26: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
29: aload_0
30: iconst_2
31: aaload
32: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: astore_1
39: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
42: aload_1
43: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
46: return
}
Как вы можете видеть, он создает StringBuilder
и использует append
. Это известно довольно неэффективно, поскольку емкость встроенного буфера по умолчанию в StringBuilder
по умолчанию равна всего 16 символам, и компилятор не знает, как распределить его заранее, поэтому он должен перераспределить. Это также куча вызовов методов. (Обратите внимание, что JVM может иногда обнаруживать и переписывать эти шаблоны вызовов, чтобы сделать их более эффективными.)
Посмотрите, что генерирует Java 9:
public class Example {
public Example();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: aload_0
1: iconst_0
2: aaload
3: aload_0
4: iconst_1
5: aaload
6: aload_0
7: iconst_2
8: aaload
9: invokedynamic #2, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
14: astore_1
15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
18: aload_1
19: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: return
}
О, мой, но это короче.:-) Он делает один вызов makeConcatWithConstants
из StringConcatFactory
, который говорит об этом в своем Javadoc:
Способы облегчения создания методов конкатенации String, которые могут быть использованы для эффективного конкатенации известного количества аргументов известных типов, возможно, после адаптации типа и частичной оценки аргументов. Эти методы обычно используются в качестве методов начальной загрузки для сайтов вызовов invokedynamic
для поддержки функции конкатенации строк языка программирования Java.
Ответ 2
На мой взгляд, invokedynamic
чем invokedynamic
в invokedynamic
реализации invokedynamic
используемой для оптимизации конкатенации строк, необходимо получить представление о том, что такое invokedynamic и как его использовать?
Инструкция invokedynamic
упрощает и потенциально улучшает реализации компиляторов и систем времени выполнения для динамических языков в JVM. Это достигается за счет того, что разработчик языка может определить пользовательское поведение связывания с invokedynamic
инструкции invokedynamic
которая включает следующие шаги.
Вероятно, я бы попытался рассказать вам об этих изменениях, которые были внесены для реализации оптимизации конкатенации строк.
-
Определение метода начальной загрузки: - В Java9 методы начальной загрузки для invokedynamic
вызываемых invokedynamic
вызовов для поддержки конкатенации строк, в первую очередь, makeConcat
и makeConcatWithConstants
были представлены в реализации StringConcatFactory
.
Использование invokedynamic предоставляет альтернативу для выбора стратегии перевода до времени выполнения. Стратегия перевода, используемая в StringConcatFactory
, аналогична LambdaMetafactory
представленному в предыдущей версии Java. Кроме того, одна из целей JEP, упомянутых в этом вопросе, заключается в том, чтобы расширить эти стратегии.
-
Указание записей пула констант: - Это дополнительные статические аргументы к invokedynamic
инструкции, отличные от (1) объекта MethodHandles.Lookup
который является фабрикой для создания дескрипторов метода в контексте invokedynamic
инструкции, (2) объекта String
, метода имя, упомянутое в сайте динамического вызова и (3) объект MethodType
, сигнатура разрешенного типа сайта динамического вызова.
Там уже есть ссылки во время связывания кода. Во время выполнения запускается метод начальной загрузки и ссылки в реальном коде выполняют конкатенацию. Он переписывает вызов invokedynamic
с соответствующим invokestatic
. Это загружает строку констант из пула констант, статические аргументы метода начальной загрузки используются для передачи этих и других констант прямо в вызов метода начальной загрузки.
-
Использование invokedynamic Instruction: - Это предлагает средства для ленивых связей, предоставляя средства для начальной загрузки цели вызова во время начального вызова. Конкретная идея для оптимизации здесь, чтобы заменить весь StringBuilder.append
танец с простым invokedynamic
вызова java.lang.invoke.StringConcatFactory
, который будет принимать значения в необходимости конкатенации.
Предложение Indify String Concatenation в качестве примера указывает на сравнительный анализ приложения с Java9, где компилируется метод, аналогичный совместно используемому @TJ Crowder, и различие в байт-коде достаточно заметно между различными реализациями.
Ответ 3
Я немного добавлю немного деталей здесь. Основная часть, которую нужно получить, состоит в том, что выполнение конкатенации строк - это время исполнения, а не время компиляции. Таким образом, это может измениться, что означает, что вы скомпилировали свой код один раз против java-9, и он может изменить базовую реализацию, но это нравится, без необходимости повторной компиляции.
И второе: в данный момент есть 6 possible strategies for concatenation of String
:
private enum Strategy {
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder}.
*/
BC_SB,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but trying to estimate the required storage.
*/
BC_SB_SIZED,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but computing the required storage exactly.
*/
BC_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also tries to estimate the required storage.
*/
MH_SB_SIZED,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also estimate the required storage exactly.
*/
MH_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that constructs its own byte[] array from
* the arguments. It computes the required storage exactly.
*/
MH_INLINE_SIZED_EXACT
}
Вы можете выбрать любой из них с помощью параметра: -Djava.lang.invoke.stringConcat
. Обратите внимание, что StringBuilder
по-прежнему является опцией.