Как реализована конкатенация строк в Java 9?

Как написано в JEP 280:

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

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

98
задан 01 окт. '17 в 16:34
источник поделиться
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."<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.

86
ответ дан 01 окт. '17 в 16:51
источник

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

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


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

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

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

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

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

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

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

20
ответ дан 01 окт. '17 в 18:17
источник

Я немного добавлю немного деталей здесь. Основная часть, которую нужно получить, состоит в том, что выполнение конкатенации строк - это время исполнения, а не время компиляции. Таким образом, это может измениться, что означает, что вы скомпилировали свой код один раз против 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 по-прежнему является опцией.

18
ответ дан 01 окт. '17 в 22:38
источник

Другие вопросы по меткам или Задайте вопрос