¿Cómo se implementa la concatenación de cadenas en Java 9?


Como está escrito en JEP 280:

Cambie la secuencia estática String-concatenación de bytecode generada por javac para usar llamadas invokedynamic a las funciones de la biblioteca JDK. Esto permitirá optimizaciones futuras de la concatenación String sin requerir cambios adicionales en el bytecode emmited por javac.

Aquí quiero entender cuál es el uso de llamadas invokedynamic y cómo la concatenación de bytes es diferente de invokedynamic?

Author: xehpuk, 2017-10-01

3 answers

La forma "antigua" genera un montón de operaciones orientadas a StringBuilder. Considere este programa:

public class Example {
    public static void main(String[] args)
    {
        String result = args[0] + "-" + args[1] + "-" + args[2];
        System.out.println(result);
    }
}

Si compilamos eso con JDK 8 o anterior y luego usamos javap -c Example para ver el bytecode, vemos algo como esto:

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
}

Como puedes ver, crea un StringBuilder y usa append. Esto es famoso bastante ineficiente ya que la capacidad predeterminada del búfer incorporado en StringBuilder es de solo 16 caracteres, y no hay forma de que el compilador sepa asignar más por adelantado, por lo que termina tener que reasignar. También es un montón de llamadas de método. (Tenga en cuenta que la JVM puede a veces detectar y reescribir estos patrones de llamadas para hacerlos más eficientes, sin embargo.)

Veamos lo que genera 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
}

Oh, pero eso es más corto. :- ) Hace una sola llamada a makeConcatWithConstants desde StringConcatFactory, que dice esto en su Javadoc:

Métodos para facilitar la creación de métodos de concatenación de cadenas, que se pueden utilizar para concatenar un número conocido de argumentos de tipos conocidos, posiblemente después de la adaptación de tipos y la evaluación parcial de argumentos. Estos métodos se usan típicamente como métodos bootstrap para invokedynamicsitios de llamadas, para soportar la característica concatenación de cadenas del Lenguaje de programación Java.

 90
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-02 16:22:09

Antes de entrar en los detalles de la implementación invokedynamic utilizada para la optimización de la concatenación de cadenas, en mi opinión, uno debe obtener algo de fondo sobre ¿Qué es invokedynamic y cómo lo uso?

El invokedynamic la instrucción simplifica y potencialmente mejora las implementaciones de compiladores y sistemas de tiempo de ejecución para lenguajes dinámicos en la JVM . Se hace esto al permitir que el implementador de lenguaje defina personalizado comportamiento de enlace con la instrucción invokedynamic que implica los siguientes pasos.


Probablemente intentaría guiarlo a través de estos con los cambios que se trajeron para la implementación de la optimización de concatenación de cadenas.

  • Definiendo el Método Bootstrap:- Con Java9, los métodos bootstrap para invokedynamic llaman a sitios, para soportar la concatenación de cadenas principalmente makeConcat y makeConcatWithConstants eran introducido con el StringConcatFactory aplicación.

    El uso de invokedynamic proporciona una alternativa para seleccionar una estrategia de traducción hasta el tiempo de ejecución. La estrategia de traducción utilizada en StringConcatFactory es similar a la LambdaMetafactory como se introdujo en la versión anterior de Java. Además, uno de los objetivos del PEC mencionado en la pregunta es ampliar aún más estas estrategias.

  • Especificar Entradas de Grupos Constantes:- Estos son los argumentos estáticos adicionales a la instrucción invokedynamic distintos de (1) MethodHandles.Lookup objeto que es una fábrica para crear manejadores de método en el contexto de la instrucción invokedynamic, (2) un objeto String, el nombre del método mencionado en el sitio de llamada dinámica y (3) el MethodType objeto, la firma de tipo resuelta del sitio de llamada dinámica.

    Ya hay enlaces durante el enlace del código. En tiempo de ejecución, el método bootstrap se ejecuta y enlaza en el código haciendo la concatenación. Reescribe la llamada invokedynamic con una llamada invokestatic apropiada. Esto carga la cadena constante del grupo de constantes, los args estáticos del método bootstrap se aprovechan para pasar estas y otras constantes directamente a la llamada al método bootstrap.

  • Usando la Instrucción invokedynamic:- Esto ofrece las facilidades para un enlace perezoso, proporcionando los medios para arrancar el destino de la llamada una vez, durante la invocación inicial. La idea concreta para la optimización aquí es reemplazar toda la danza StringBuilder.append con una simple llamada invokedynamic a java.lang.invoke.StringConcatFactory, que aceptará los valores en la necesidad de concatenación.

La propuesta Indify String Concatenation establece con un ejemplo el benchmarking de la aplicación con Java9 donde se compila un método similar al compartido por @T. J. Crowder y la diferencia en el bytecode es bastante visible aplicación.

 21
Author: nullpointer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-19 07:33:09

Voy a añadir un poco de detalles aquí. La parte principal a obtener es que cómo se realiza la concatenación de cadenas es una decisión en tiempo de ejecución , no una en tiempo de compilación. Por lo tanto, puede cambiar, lo que significa que ha compilado su código una vez contra java-9 y puede cambiar la implementación subyacente como quiera, sin la necesidad de volver a compilar.

Y el segundo punto es que en este momento hay 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
}

Puede elegir cualquiera de ellos a través de un parámetro : -Djava.lang.invoke.stringConcat. Observe que StringBuilder sigue siendo una opción.

 18
Author: Eugene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-01 19:38:38