Как написано в JEP 280: Указать конкатенацию строк:

Измените последовательность байт-кода статической String-конкатенации, сгенерированную javac, чтобы использовать вызовы invokedynamic к функциям библиотеки JDK. Это позволит в будущем оптимизировать конкатенацию String, не требуя дальнейших изменений байт-кода, созданного javac.

Здесь я хочу понять, в чем заключается использование вызовов invokedynamic и чем конкатенация байт-кода отличается от invokedynamic?

Ответы (3)

"Старый" способ вывода кучу 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."":()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."":()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."":()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:

Методы, облегчающие создание методов конкатенации строк, которые могут использоваться для эффективного конкатенации известного количества аргументов известных типов, возможно, после адаптации типа и частичной оценки аргументов. Эти методы обычно используются как методы начальной загрузки для invokedynamic call sites, для поддержки конкатенации строк языка программирования Java.

Прежде чем вдаваться в подробности реализации invokedynamic, используемой для оптимизации конкатенации строк, на мой взгляд, нужно получить некоторую справочную информацию о Что invokedynamic и как его использовать?

invokedynamic инструкция упрощает и потенциально улучшает реализации компиляторы и системы времени выполнения для динамических языков на JVM. Это делает это, позволяя разработчику языка определять пользовательские поведение связи с инструкцией invokedynamic, которая включает выполните следующие шаги.


Я бы, вероятно, попытался рассказать вам об изменениях, которые были внесены для реализации оптимизации конкатенации строк.

  • Определение метода начальной загрузки: - В Java9 методы начальной загрузки для invokedynamic вызывают сайты, для поддержки конкатенации строк в первую очередь makeConcat и makeConcatWithConstants были представлены с реализацией StringConcatFactory.

    Использование invokedynamic предоставляет альтернативу для выбора стратегии трансляции до времени выполнения. Стратегия перевода, используемая в StringConcatFactory, аналогична LambdaMetafactory, представленной в предыдущей версии java. Кроме того, одной из целей JEP, упомянутой в вопросе, является дальнейшее расширение этих стратегий.

  • Указание записей постоянного пула: - Это дополнительные статические аргументы для инструкции invokedynamic, кроме (1) MethodHandles.Lookup* объект *, который является фабрикой для создания дескрипторов методов в контексте инструкции invokedynamic, (2) объект String,, имя метода, упомянутое на сайте динамического вызова и (3) объект MethodType, разрешенная сигнатура типа сайта динамического вызова.

    Есть уже ссылки при привязке кода. Во время выполнения запускается метод начальной загрузки и связывается с фактическим кодом, выполняющим конкатенацию. Он перезаписывает вызов invokedynamic соответствующим вызовом invokestatic.Это загружает постоянную строку из пула констант, статические аргументы метода начальной загрузки используются для передачи этих и других констант прямо в вызов метода начальной загрузки.

  • Использование инструкции invokedynamic: - Это предлагает средства для ленивого связывания, предоставляя средства для однократной начальной загрузки цели вызова во время первоначального вызова.Конкретная идея оптимизации здесь состоит в том, чтобы заменить весь StringBuilder.append танец простым invokedynamic вызовом java.lang.invoke.StringConcatFactory, который примет значения, требующие объединения.

В предложении Indify String Concatenation на примере приводится сравнительный анализ приложения с Java9, в котором используется аналогичный метод, используемый @T.J. Crowder скомпилирован, и разница в байт-коде довольно заметна между различными реализациями.

Я немного добавлю здесь немного деталей. Главное, что нужно понять, это то, как выполняется конкатенация строк, - это решение времени выполнения, а не время компиляции. Таким образом, он может измениться, что означает, что вы скомпилировали свой код один раз против java-9, и он может изменить базовую реализацию, как угодно, без необходимости повторной компиляции.

И второй момент заключается в том, что на данный момент существует 6 возможных стратегий конкатенации 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 все еще возможен.

2022 WebDevInsider