如何在Java 9中实现String连接?

Moh*_*agi 108 java string string-concatenation invokedynamic java-9

正如JEP 280中所写:

更改String生成的static -concatenation字节码序列,javac以使用invokedynamic对JDK库函数的调用.这将使未来的String串联优化成为可能,而无需进一步更改所设置的字节码javac.

在这里,我想了解invokedynamic调用的用途以及字节码串联的不同之处是invokedynamic什么?

T.J*_*der 93

"旧"方式输出一堆StringBuilder定向操作.考虑这个程序:

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

如果我们使用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
}

哦,我的,但那更短.:-)它对makeConcatWithConstantsfrom 进行一次调用StringConcatFactory,在Javadoc中说这个:

方便创建字符串连接方法的方法,可用于有效地连接已知类型的已知类型的参数,可能在类型调整和参数的部分评估之后.这些方法通常用作引导方法invokedynamic调用处,支持字符串连接 Java编程语言的特点.

  • 这让我想起了我差不多6年前写的答案:/sf/answers/531074631/ - 有人问他们是否应该制作一个StringBuilder,或者只是在他们的作品中使用普通的`+ =`环.我告诉他们这取决于,但是我们不要忘记,他们可能会找到一种更好的方法来串联concat.关键是真正的倒数第二行:"因此,当你比你聪明时,你已经引起了性能的打击.` (39认同)
  • @corsiKa:哈哈!但哇,花了很长时间才到达那里(我不是说六年,我的意思是22左右...... :-)) (3认同)
  • @supercat我指的是已经存在的`String.concat(String)`方法,该方法的实现是在原地创建结果字符串的数组.当我们必须在任意对象上调用`toString()`时,优势变得毫无意义.同样,当调用接受数组的方法时,调用者必须创建并填充数组,这会降低整体收益.但现在,这是无关紧要的,因为新的解决方案基本上是你正在考虑的,除了它没有拳击开销,不需要创建数组,后端可以为特定场景生成优化的处理程序. (2认同)

Nam*_*man 20

在进入invokedynamic用于优化字符串连接的实现的细节之前,在我看来,必须得到一些关于什么是invokedynamic的背景知识以及如何使用它?

invokedynamic 指令简化并可能改进JVM上动态语言的编译器和运行时系统的实现.它通过允许语言实现者使用invokedynamic涉及以下步骤的指令定义自定义链接行为来实现此目的.


我可能会尝试通过为实现字符串连接优化而带来的更改来引导您完成这些操作.

  • 定义Bootstrap方法: - 使用Java9,invokedynamic调用站点的引导方法,主要支持字符串连接makeConcat,makeConcatWithConstants并随StringConcatFactory实现一起引入.

    使用invokedynamic提供了在运行时选择转换策略的替代方法.使用的转换策略StringConcatFactory类似于LambdaMetafactory之前的java版本中引入的转换策略.此外,问题中提到的JEP的目标之一是进一步扩展这些策略.

  • 指定常量池条目: - 这些是除了invokedynamic(1)MethodHandles.Lookup对象之外的指令的附加静态参数,该对象是用于在invokedynamic指令的上下文中创建方法句柄的工厂,(2)String对象,动态调用中提到的方法名称站点和(3)MethodType对象,动态调用站点的已解析类型签名.

    在链接代码期间已经链接了.在运行时,引导方法运行并链接到实际代码中进行连接.invokedynamic通过适当的invokestatic呼叫重写呼叫.这将从常量池加载常量字符串,利用bootstrap方法static args将这些和其他常量直接传递给bootstrap方法调用.

  • 使用invokedynamic指令: - 通过在初始调用期间提供一次引导调用目标的方法,为延迟链接提供了便利.这里优化的具体想法是StringBuilder.append用简单的invokedynamic调用替换整个舞蹈java.lang.invoke.StringConcatFactory,这将接受需要连接的值.

所述Indify字符串连接建议用一个例子指出与Java9应用程序,其中一个相似的方法如通过共享的基准@TJ克劳德被编译并在字节码的差变化的执行之间相当可见.


Eug*_*ene 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
}
Run Code Online (Sandbox Code Playgroud)

您可以通过参数选择其中任何一个:-Djava.lang.invoke.stringConcat.请注意,这StringBuilder仍然是一个选项.