【发布时间】:2021-07-01 00:21:11
【问题描述】:
上下文:我一直在对使用 invokedynamic 和手动生成字节码之间的区别进行基准测试(这是在决定针对 JVM 的编译器是否应该发出更冗长的“传统”字节码的上下文中或者只是一个 invokedynamic 调用一个聪明的引导方法)。在此过程中,将字节码映射到至少同样快的 MethodHandles 组合子非常简单,tableswitch 除外。
问题: 有没有使用MethodHandle 模仿tableswitch 的技巧?我尝试用跳转表来模仿它:使用常量MethodHandle[],用arrayElementGetter 对其进行索引,然后用MethodHandles.invoker 调用找到的句柄。然而,当我通过 JMH 运行它时,它最终比原始字节码慢了大约 50%。
下面是生成方法句柄的代码:
private static MethodHandle makeProductElement(Class<?> receiverClass, List<MethodHandle> getters) {
MethodHandle[] boxedGetters = getters
.stream()
.map(getter -> getter.asType(getter.type().changeReturnType(java.lang.Object.class)))
.toArray(MethodHandle[]::new);
MethodHandle getGetter = MethodHandles // (I)H
.arrayElementGetter(MethodHandle[].class)
.bindTo(boxedGetters);
MethodHandle invokeGetter = MethodHandles.permuteArguments( // (RH)O
MethodHandles.invoker(MethodType.methodType(java.lang.Object.class, receiverClass)),
MethodType.methodType(java.lang.Object.class, receiverClass, MethodHandle.class),
1,
0
);
return MethodHandles.filterArguments(invokeGetter, 1, getGetter);
}
这是初始字节码(我试图用一个 invokedynamic 调用替换它)
public java.lang.Object productElement(int);
descriptor: (I)Ljava/lang/Object;
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=3, args_size=2
0: iload_1
1: istore_2
2: iload_2
3: tableswitch { // 0 to 2
0: 28
1: 38
2: 45
default: 55
}
28: aload_0
29: invokevirtual #62 // Method i:()I
32: invokestatic #81 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
35: goto 67
38: aload_0
39: invokevirtual #65 // Method s:()Ljava/lang/String;
42: goto 67
45: aload_0
46: invokevirtual #68 // Method l:()J
49: invokestatic #85 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
52: goto 67
55: new #87 // class java/lang/IndexOutOfBoundsException
58: dup
59: iload_1
60: invokestatic #93 // Method java/lang/Integer.toString:(I)Ljava/lang/String;
63: invokespecial #96 // Method java/lang/IndexOutOfBoundsException."<init>":(Ljava/lang/String;)V
66: athrow
67: areturn
【问题讨论】:
-
它是否必须是“暂时离开”的方法?引导方法可以决定生成包含切换指令的字节码或返回组合方法句柄。
StringConcatFactory的参考实现,举一个实际的例子,能够同时生成代码或组成句柄,当然,编译器生成的invokedynamic指令不受该运行时选择的影响。 -
@Holger 我根本没有考虑过这种选择,如果可能的话,那确实没问题。你有
StringConcatFactory相关部分的链接吗?我找不到任何直接从字节码构造MethodHandle的示例。 -
这不是“直接从字节码”,而是从字节码创建一个类,并返回该类方法的句柄。首选机制是defineHiddenClass,它允许生成的类调用查找类的
private方法。这已成为 JDK 15 中的标准功能,之前的版本为此使用了sun.misc.Unsafe。 -
@Holger 感谢您的指点! AFAICT,
defineHiddenClass根本不用于字符串 concat 的东西,但我确实在java.lang.invoke.InnerClassLambdaMetafactory中找到了一个合法(并且不足为奇)的用法。如果您想为某些tableswitch指令汇总一个执行此操作的示例(即使它与我的问题的功能不完全匹配)我会接受
标签: jvm bytecode methodhandle invokedynamic