【问题标题】:How is String concatenation implemented in Java 9?Java 9 中如何实现字符串连接?
【发布时间】:2018-03-12 18:42:17
【问题描述】:

JEP 280: Indify String Concatenation中所写:

javac 生成的静态String-concatenation 字节码序列更改为使用invokedynamic 调用JDK 库函数。这将支持String 连接的未来优化,而无需进一步更改javac 发出的字节码。

这里想了解invokedynamic调用的用途是什么,字节码拼接和invokedynamic有什么不同?

【问题讨论】:

  • wrote about that 不久前 - 如果有帮助,我会将其浓缩为答案。
  • 另外,看看这个视频,它很好地解释了新字符串连接机制的要点:youtu.be/wIyeOaitmWM?t=37m58s
  • @ZhekaKozlov 我希望我能对您的评论进行两次投票,来自实际实施所有这些的人的链接是最好的。
  • @Nicolai:那太好了,而且是比这里任何其他人(包括我的)更好的答案。我的答案的任何部分你想在你这样做时加入,请随意 - 如果你(基本上)将整个事情作为更广泛答案的一部分包括在内,我将删除我的。或者,如果您只想添加到我的答案中,因为它很明显,我已将其设为社区 wiki。

标签: java string string-concatenation java-9 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 查看字节码,我们会看到如下内容:

公共类示例{ 公共示例(); 代码: 0:aload_0 1: invokespecial #1 // 方法 java/lang/Object."":()V 4:返回 公共静态无效主(java.lang.String[]); 代码: 0: new #2 // 类 java/lang/StringBuilder 3:重复 4: invokespecial #3 // 方法 java/lang/StringBuilder."":()V 7:aload_0 8:iconst_0 9:加载 10: invokevirtual #4 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 13: ldc #5 // 字符串 - 15: invokevirtual #4 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 18:加载_0 19:iconst_1 20:加载 21: invokevirtual #4 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: ldc #5 // 字符串 - 26: invokevirtual #4 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 29:加载_0 30:图标st_2 31:加载 32: invokevirtual #4 // 方法 java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 35: invokevirtual #6 // 方法 java/lang/StringBuilder.toString:()Ljava/lang/String; 38:astore_1 39: getstatic #7 // 字段 java/lang/System.out:Ljava/io/PrintStream; 42:加载_1 43: invokevirtual #8 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V 46:返回 }

如您所见,它创建了一个StringBuilder 并使用append。这是众所周知的相当低效的,因为StringBuilder 中内置缓冲区的默认容量只有 16 个字符,并且 编译器 无法知道提前分配更多,所以它结束了不得不重新分配。它也是一堆方法调用。 (请注意,JVM有时可以检测并重写这些调用模式以提高它们的效率。)

让我们看看 Java 9 生成了什么:

公共类示例{ 公共示例(); 代码: 0:aload_0 1: invokespecial #1 // 方法 java/lang/Object."":()V 4:返回 公共静态无效主(java.lang.String[]); 代码: 0:aload_0 1:iconst_0 2:加载 3:aload_0 4:iconst_1 5:加载 6:aload_0 7:iconst_2 8:加载 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 // 字段 java/lang/System.out:Ljava/io/PrintStream; 18:加载_1 19: invokevirtual #4 // 方法 java/io/PrintStream.println:(Ljava/lang/String;)V 22:返回 }

哦,天哪,但那更短了。 :-) 它从StringConcatFactorymakeConcatWithConstants 进行一次调用,这在其Javadoc 中说明了这一点:

有助于创建字符串连接方法的方法,可用于有效连接已知类型的已知数量的参数,可能在类型适应和参数的部分评估之后。这些方法通常用作invokedynamic 呼叫站点的引导方法,以支持Java 编程语言的字符串连接 功能。

【讨论】:

  • 这让我想起了大约 6 年前写的一个答案:stackoverflow.com/a/7586780/330057 - 有人问他们是否应该制作一个 StringBuilder 或者只是在他们的 for 循环中使用普通的旧 +=。我告诉他们这要看情况而定,但我们不要忘记他们可能会在以后的某个时候找到更好的方法来串接 concat。关键行真的是倒数第二行:So by being smart, you have caused a performance hit when Java got smarter than you.
  • @corsiKa:哈哈!但是哇,到那里花了很长时间(我不是说六年,我是说 22 年左右...... :-))
  • @supercat:据我了解,有几个原因,尤其是创建一个可变参数数组以传递给性能关键路径上的方法并不理想。此外,使用invokedynamic 允许在运行时选择不同的连接策略并在第一次调用时绑定,而无需在每次调用时产生方法调用和调度表的开销;更多内容见nicolai's 文章herethe JEP
  • @supercat:还有一个事实是它不能很好地处理非字符串,因为它们必须预先转换为字符串而不是转换为最终结果;更无效率。可以做到Object,但是你必须把所有的原语装箱......(顺便说一句,Nicolai 在他的优秀文章中介绍了。)
  • @supercat 我指的是已经存在的String.concat(String) 方法,它的实现是就地创建结果字符串的数组。当我们必须在任意对象上调用 toString() 时,优势就变得没有意义了。同样,当调用接受数组的方法时,调用者必须创建并填充数组,这会降低整体收益。但是现在,它已经无关紧要了,因为新的解决方案基本上就是您正在考虑的,只是它没有装箱开销,不需要创建数组,并且后端可能会为特定场景生成优化的处理程序。
【解决方案2】:

在详细介绍用于优化字符串连接的invokedynamic 实现之前,在我看来,必须了解What's invokedynamic and how do I use it? 的背景

invokedynamic 指令简化并可能改进 JVM 上动态语言的编译器和运行时系统。它 通过允许语言实现者定义自定义来做到这一点 与invokedynamic 指令的链接行为涉及 遵循以下步骤。


我可能会尝试带您了解这些为实现字符串连接优化而带来的更改。

  • 定义引导方法:- 在 Java9 中,invokedynamic 调用站点的引导方法,主要支持字符串连接 makeConcatmakeConcatWithConstants 是通过 StringConcatFactory 实现引入的。

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

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

    在代码的链接过程中已经有链接。在运行时,bootstrap 方法运行并链接到执行串联的实际代码中。它使用适当的 invokestatic 调用重写 invokedynamic 调用。 这会从常量池中加载常量字符串,利用引导方法静态参数将这些和其他常量直接传递给引导方法调用。

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

Indify String Concatenation 提案通过示例说明了使用 Java9 对应用程序进行基准测试,其中编译了 @T.J. 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 仍然是一个选项。

    【讨论】:

      猜你喜欢
      • 2020-01-02
      • 2017-12-23
      • 2016-12-17
      • 2016-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-02
      • 1970-01-01
      相关资源
      最近更新 更多