Ale*_*lec 6 jvm bytecode invokedynamic methodhandle
上下文:我一直在对使用invokedynamic
和手动生成字节码之间的差异进行基准测试(这是在决定面向 JVM 的编译器是应该发出更冗长的“传统”字节码还是只是invokedynamic
使用巧妙的引导方法调用)的上下文中。在这样做时,将字节码映射到MethodHandles
至少同样快的组合器是非常简单的,除了tableswitch
.
问题:是否有模仿tableswitch
使用的技巧MethodHandle
?我试着用一个跳转表来模仿它:使用一个常量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);
}
Run Code Online (Sandbox Code Playgroud)
这是初始字节码(我试图用一个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
Run Code Online (Sandbox Code Playgroud)
好处invokedynamic
是它允许推迟决定如何实施操作到实际运行时。这就是背后的技巧LambdaMetafactory
或者StringConcatFactory
可能会返回组合方法句柄,如示例代码或动态生成的代码,由特定的实现\xe2\x80\x99s自行决定。
\xe2\x80\x99s 甚至可以采用组合方法,生成您组成操作的类,例如解决已经存在的LambdaMetafactory
:
private static MethodHandle makeProductElement(\n MethodHandles.Lookup lookup, Class<?> receiverClass, List<MethodHandle> getters)\n throws Throwable {\n\n Function[] boxedGetters = new Function[getters.size()];\n MethodType factory = MethodType.methodType(Function.class);\n for(int ix = 0; ix < boxedGetters.length; ix++) {\n MethodHandle mh = getters.get(ix);\n MethodType actual = mh.type().wrap(), generic = actual.erase();\n boxedGetters[ix] = (Function)LambdaMetafactory.metafactory(lookup,\n "apply", factory, generic, mh, actual).getTarget().invokeExact();\n }\n\n Object switcher = new Object() {\n final Object get(Object receiver, int index) {\n return boxedGetters[index].apply(receiver);\n }\n };\n return lookup.bind(switcher, "get",\n MethodType.methodType(Object.class, Object.class, int.class))\n .asType(MethodType.methodType(Object.class, receiverClass, int.class));\n}\n
Run Code Online (Sandbox Code Playgroud)\n这使用为每个 getterLambdaMetafactory
生成一个Function
实例,类似于等效方法引用。Function
然后,调用正确的\xe2\x80\x99s方法的实际类apply
被实例化,并且其方法句柄get
。
这是与您的方法句柄类似的组合,但对于参考实现,不使用句柄,而是使用完全具体化的类。I\xe2\x80\x99d 希望组合句柄和此方法对于大量调用能够收敛到相同的性能,但物化类对于中等数量的调用具有领先优势。
\n我添加了第一个参数MethodHandles.Lookup lookup
,它应该是指令lookup
的引导方法接收的对象invokedynamic
。如果以这种方式使用,生成的函数可以像包含指令的代码一样访问所有方法invokedynamic
,包括private
该类的方法。
或者,您可以自己生成一个包含真实 switch 指令的类。使用ASM 库,它可能看起来像:
\nprivate static MethodHandle makeProductElement(\n MethodHandles.Lookup lookup, Class<?> receiverClass, List<MethodHandle> getters)\n throws ReflectiveOperationException {\n\n ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);\n cw.visit(V1_8, ACC_INTERFACE|ACC_ABSTRACT,\n lookup.lookupClass().getName().replace(\'.\', \'/\')+"$Switch", null,\n "java/lang/Object", null);\n MethodType type = MethodType.methodType(Object.class, receiverClass, int.class);\n MethodVisitor mv = cw.visitMethod(ACC_STATIC|ACC_PUBLIC, "get",\n type.toMethodDescriptorString(), null, null);\n mv.visitCode();\n\n Label defaultCase = new Label();\n Label[] cases = new Label[getters.size()];\n for(int ix = 0; ix < cases.length; ix++) cases[ix] = new Label();\n\n mv.visitVarInsn(ALOAD, 0);\n mv.visitVarInsn(ILOAD, 1);\n mv.visitTableSwitchInsn(0, cases.length - 1, defaultCase, cases);\n\n String owner = receiverClass.getName().replace(\'.\', \'/\');\n\n for(int ix = 0; ix < cases.length; ix++) {\n mv.visitLabel(cases[ix]);\n MethodHandle mh = getters.get(ix);\n mv.visitMethodInsn(INVOKEVIRTUAL, owner, lookup.revealDirect(mh).getName(),\n mh.type().dropParameterTypes(0, 1).toMethodDescriptorString(), false);\n if(mh.type().returnType().isPrimitive()) {\n Class<?> boxed = mh.type().wrap().returnType();\n MethodType box = MethodType.methodType(boxed, mh.type().returnType());\n mv.visitMethodInsn(INVOKESTATIC, boxed.getName().replace(\'.\', \'/\'),\n "valueOf", box.toMethodDescriptorString(), false);\n }\n mv.visitInsn(ARETURN);\n }\n mv.visitLabel(defaultCase);\n mv.visitTypeInsn(NEW, "java/lang/IndexOutOfBoundsException");\n mv.visitInsn(DUP);\n mv.visitVarInsn(ILOAD, 1);\n mv.visitMethodInsn(INVOKESTATIC, "java/lang/String",\n "valueOf", "(I)Ljava/lang/String;", false);\n mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IndexOutOfBoundsException",\n "<init>", "(Ljava/lang/String;)V", false);\n mv.visitInsn(ATHROW);\n mv.visitMaxs(-1, -1);\n mv.visitEnd();\n cw.visitEnd();\n\n lookup = lookup.defineHiddenClass(\n cw.toByteArray(), true, MethodHandles.Lookup.ClassOption.NESTMATE);\n return lookup.findStatic(lookup.lookupClass(), "get", type);\n}\n
Run Code Online (Sandbox Code Playgroud)\n这会生成一个新类,其static
方法包含tableswitch
指令和调用(以及我们现在必须自己完成的装箱转换)。此外,它还具有创建和抛出越界值异常所需的代码。生成类后,它返回该static
方法的句柄。