问题
更改javac生成的静态字符串连接字节码序列,以使用对JDK库函数的invokedynamic调用。这将使得能够在将来优化字符串连接,而无需进一步更改由javac发出的字节码。
在这里,我想了解invokedynamiccalls的用途以及字节码串联与invokedynamic的不同之处?
#1 热门回答(90 赞)
"旧"方式输出了一堆面向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."":()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."":()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
}
如你所见,它创建了aStringBuilder并使用了append。这是着名的效率相当低,因为StringBuilder中内置缓冲区的默认容量只有16个字符,并且没有办法让编程提前分配更多,因此最终必须重新分配。它也是一堆方法调用。 (请注意,JVM有时可以检测并重写这些调用模式,以提高它们的效率。)
让我们看看Java 9生成的内容:
public class Example {
public Example();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()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
}
哦,我的,但那更短。 :-)只需拨打634733518,StringConcatFactory,就可以在Javadoc中说这个:
方便创建字符串连接方法的方法,可用于有效地连接已知类型的已知类型的参数,可能在类型调整和参数的部分评估之后。这些方法通常用作invokedynamic调用站点的引导方法,以支持Java编程语言的字符串连接功能。
#2 热门回答(21 赞)
在进入用于优化字符串连接的225l40681实现的细节之前,在我看来,必须得到一些背景overWhat's invokedynamic and how do I use it?
invokedynamic指令简化并可能改进JVM上动态语言的编译器和运行时系统的实现。它通过允许语言实现者使用invokedynamic指令定义自定义链接行为来完成此操作,该指令涉及以下步骤。
我可能会尝试通过为字符串连接优化的实现带来的更改来引导你完成这些操作。
定义Bootstrap方法: - 使用Java9,invokedynamic调用站点的bootstrap方法,以支持字符串连接,主要是makeConcat和makeConcatWithConstants与StringConcatFactory实现一起引入。 invokedynamic的使用提供了在运行时选择转换策略的替代方法。 StringConcatFactory中使用的转换策略类似于之前java版本中引入的LambdaMetafactory。此外,问题中提到的JEP的目标之一是进一步扩展这些策略。
指定常量池条目: - 这些是invokedynamic指令的附加静态参数,而不是(1)MethodHandles.Lookup对象,它是在invokedynamic指令的上下文中创建方法句柄的工厂,(2)一个String对象,动态调用站点中提到的方法名称和(3)MethodType对象,动态调用站点的已解析类型签名。在链接代码期间已经链接了。在运行时,引导方法运行并在实际代码中链接进行连接。它使用适当的invokestatic调用重写invokedynamic调用。这将从常量池中加载常量字符串,利用bootstrap方法static args将这些和其他常量直接传递给bootstrap方法调用。
使用invokedynamic指令: - 通过在初始调用期间提供一次引导调用目标的方法,这为延迟链接提供了便利。这里优化的具体想法是用对java.lang.invoke.StringConcatFactory的简单invokedynamic调用替换整个StringBuilder.append舞,它将接受需要连接的值。
Indify String Concatenation提出了一个示例,其中使用Java9对应用程序进行基准测试,其中编译了由@T.J. Crowder共享的类似方法,并且字节码的差异在变化的实现之间是相当明显的。
#3 热门回答(18 赞)
我在这里稍微添加一些细节。要获得的主要部分是字符串连接的完成是a运行时决定,而不是编译时间。因此它可以更改,这意味着你已经编译了对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仍然是一个选项。