如JEP 280:Indify String Concatenation:
更改由
String
生成的静态-concatenation字节码序列,javac
以使用invokedynamic
对JDK库函数的调用。这将使将来的String
连接优化成为可能,而无需进一步更改所发出的字节码javac
。
在这里,我想了解invokedynamic
调用的用途是什么,字节码的连接与有何不同invokedynamic
?
如JEP 280:Indify String Concatenation:
更改由
String
生成的静态-concatenation字节码序列,javac
以使用invokedynamic
对JDK库函数的调用。这将使将来的String
连接优化成为可能,而无需进一步更改所发出的字节码javac
。
在这里,我想了解invokedynamic
调用的用途是什么,字节码的连接与有何不同invokedynamic
?
Answers:
“旧”方式输出一堆 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
查看字节码,则会看到类似以下内容的内容:
公共类示例{ 公共Example(); 码: 0:加载_0 1:invokespecial#1 //方法java / lang / Object。“ <init>” :()V 4:返回 公共静态void main(java.lang.String []); 码: 0:新#2 //类java / lang / StringBuilder 3:dup 4:invokespecial#3 //方法java / lang / StringBuilder。“ <init>” :()V 7:aload_0 8:iconst_0 9:aaload 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:aload_0 19:iconst_1 20:aaload 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:aload_0 30:iconst_2 31:aaload 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:aload_1 43:invokevirtual#8 //方法java / io / PrintStream.println:(Ljava / lang / String;)V 46:返回 }
如您所见,它创建了StringBuilder
并使用append
。由于内置缓冲区的默认容量StringBuilder
仅为16个字符,这是众所周知的效率很低的问题,并且编译器无法知道要提前分配更多内容,因此最终不得不重新分配。这也是一堆方法调用。(请注意,JVM 有时可以检测并重写这些调用模式,以使它们更有效。)
让我们看一下Java 9生成的内容:
公共类示例{ 公共Example(); 码: 0:加载_0 1:invokespecial#1 //方法java / lang / Object。“ <init>” :()V 4:返回 公共静态void main(java.lang.String []); 码: 0:加载_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 //字段java / lang / System.out:Ljava / io / PrintStream; 18:aload_1 19:invokevirtual#4 //方法java / io / PrintStream.println:(Ljava / lang / String;)V 22:返回 }
哦,我的,但是短些。:-)对makeConcatWithConstants
from 进行一次调用StringConcatFactory
,该调用在其Javadoc中这样说:
有助于创建String串联方法的方法,这些方法可用于有效地串联已知数量的已知类型的自变量,可能在类型自适应和部分自变量评估之后。这些方法通常用作引导方法的
invokedynamic
调用处,支持字符串连接 Java编程语言的特点。
+=
在其for循环中使用简单的old 。我告诉他们这取决于情况,但请不要忘记,他们可能会在某个时候找到更好的方法来串连。关键线确实是倒数第二行:So by being smart, you have caused a performance hit when Java got smarter than you.
invokedynamic
允许在运行时选择不同的串联策略并在第一次调用时绑定,而无需每次调用都产生方法调用和调度表的开销。更Nicolai的文章在这里,并在该JEP。
Object
,但是然后您必须将所有原语装箱……(尼古拉在他的出色文章中提到,顺便说一句)
String.concat(String)
方法,其实现是就地创建结果字符串的数组。当我们必须toString()
对任意对象进行调用时,优点变得毫无意义。同样,当调用接受数组的方法时,调用者必须创建并填充数组,这会降低总体收益。但是现在,这已经无关紧要了,因为新解决方案基本上就是您正在考虑的东西,除了它没有装箱费用,不需要创建数组而且后端可以为特定场景生成优化的处理程序。
invokedynamic
在我看来,在深入探讨用于优化字符串连接的实现的细节之前,必须先了解一下什么是invokedynamic,以及如何使用它?
该
invokedynamic
指令简化并可能改善JVM上用于动态语言的编译器和运行时系统的实现。它通过允许语言实现者使用invokedynamic
涉及以下以下步骤的指令来定义自定义链接行为来实现此目的。
我可能会尝试并带您完成为实现String串联优化所带来的更改。
定义Bootstrap方法:-在Java9中,invokedynamic
调用站点的bootstrap方法主要支持字符串连接makeConcat
,makeConcatWithConstants
并随StringConcatFactory
实现一起引入。
使用invokedynamic可以选择一种转换策略,直到运行时。中使用的翻译策略StringConcatFactory
类似于LambdaMetafactory
先前的Java版本中引入的策略。另外,问题中提到的JEP的目标之一是进一步扩展这些策略。
指定常量池条目:-这些是invokedynamic
指令的附加静态参数,而不是(1)MethodHandles.Lookup
对象,该对象是在invokedynamic
指令上下文中创建方法句柄的工厂,(2)一个String
对象,在动态调用中提到的方法名称站点和(3)MethodType
对象,即动态调用站点的已解析类型签名。
在代码链接期间已经存在链接。在运行时,bootstrap方法将运行并链接到进行级联的实际代码中。它将invokedynamic
使用适当的invokestatic
呼叫重写该呼叫。这将从常量池中加载常量字符串,利用bootstrap方法的静态args将这些常量和其他常量直接传递给bootstrap方法调用。
使用invokedynamic指令:-通过提供在初始调用期间一次引导调用目标的方法,这为延迟链接提供了便利。优化的具体思路是StringBuilder.append
,通过简单invokedynamic
调用替换整个舞蹈,该调用java.lang.invoke.StringConcatFactory
将接受需要连接的值。
所述Indify字符串连接用一个例子提案状态与Java9应用程序,其中一个相似的方法如通过共享的基准@TJ克劳德被编译并在字节码的差的变实现之间相当可见。
我将在这里稍微添加一些细节。得到的主要部分是如何完成字符串连接是一个运行时决定,而不是编译时决定。因此,它可以更改,这意味着您已经针对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
仍然是一个选择。