gpt4 book ai didi

jvm - 如何使用 `tableswitch` 模拟 `MethodHandle` ?

转载 作者:行者123 更新时间:2023-12-03 14:06:00 26 4
gpt4 key购买 nike

上下文:我一直在对使用 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);
}
这是初始字节码(我试图用一个 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

最佳答案

关于invokedynamic的好处是它允许推迟决定,如何将操作实现到实际运行时。这就是背后的窍门LambdaMetafactoryStringConcatFactory它可能会返回组合的方法句柄,如您的示例代码或动态生成的代码,由特定实现自行决定。
甚至有可能的组合方法,生成您组合到操作中的类,例如解决现有的 LambdaMetafactory :

private static MethodHandle makeProductElement(
MethodHandles.Lookup lookup, Class<?> receiverClass, List<MethodHandle> getters)
throws Throwable {

Function[] boxedGetters = new Function[getters.size()];
MethodType factory = MethodType.methodType(Function.class);
for(int ix = 0; ix < boxedGetters.length; ix++) {
MethodHandle mh = getters.get(ix);
MethodType actual = mh.type().wrap(), generic = actual.erase();
boxedGetters[ix] = (Function)LambdaMetafactory.metafactory(lookup,
"apply", factory, generic, mh, actual).getTarget().invokeExact();
}

Object switcher = new Object() {
final Object get(Object receiver, int index) {
return boxedGetters[index].apply(receiver);
}
};
return lookup.bind(switcher, "get",
MethodType.methodType(Object.class, Object.class, int.class))
.asType(MethodType.methodType(Object.class, receiverClass, int.class));
}
这使用 LambdaMetafactory生成 Function每个 getter 的实例,类似于等效的方法引用。然后,一个实际的类调用了正确的 Functionapply方法被实例化,并且是其 get 的方法句柄方法返回。
这是与您的方法句柄类似的组合,但对于引用实现,不使用句柄而是使用完全物化的类。我希望组合句柄和这种方法在大量调用时收敛到相同的性能,但物化类在中等数量的调用中具有领先优势。
我添加了第一个参数 MethodHandles.Lookup lookup这应该是 lookupinvokedynamic 的引导方法接收的对象操作说明。如果以这种方式使用,生成的函数可以以与包含 invokedynamic 的代码相同的方式访问所有方法。指令,包括 private该类的方法。
或者,您可以自己生成一个包含真正 switch 指令的类。使用 the ASM library ,它可能看起来像:
private static MethodHandle makeProductElement(
MethodHandles.Lookup lookup, Class<?> receiverClass, List<MethodHandle> getters)
throws ReflectiveOperationException {

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_8, ACC_INTERFACE|ACC_ABSTRACT,
lookup.lookupClass().getName().replace('.', '/')+"$Switch", null,
"java/lang/Object", null);
MethodType type = MethodType.methodType(Object.class, receiverClass, int.class);
MethodVisitor mv = cw.visitMethod(ACC_STATIC|ACC_PUBLIC, "get",
type.toMethodDescriptorString(), null, null);
mv.visitCode();

Label defaultCase = new Label();
Label[] cases = new Label[getters.size()];
for(int ix = 0; ix < cases.length; ix++) cases[ix] = new Label();

mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitTableSwitchInsn(0, cases.length - 1, defaultCase, cases);

String owner = receiverClass.getName().replace('.', '/');

for(int ix = 0; ix < cases.length; ix++) {
mv.visitLabel(cases[ix]);
MethodHandle mh = getters.get(ix);
mv.visitMethodInsn(INVOKEVIRTUAL, owner, lookup.revealDirect(mh).getName(),
mh.type().dropParameterTypes(0, 1).toMethodDescriptorString(), false);
if(mh.type().returnType().isPrimitive()) {
Class<?> boxed = mh.type().wrap().returnType();
MethodType box = MethodType.methodType(boxed, mh.type().returnType());
mv.visitMethodInsn(INVOKESTATIC, boxed.getName().replace('.', '/'),
"valueOf", box.toMethodDescriptorString(), false);
}
mv.visitInsn(ARETURN);
}
mv.visitLabel(defaultCase);
mv.visitTypeInsn(NEW, "java/lang/IndexOutOfBoundsException");
mv.visitInsn(DUP);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String",
"valueOf", "(I)Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IndexOutOfBoundsException",
"<init>", "(Ljava/lang/String;)V", false);
mv.visitInsn(ATHROW);
mv.visitMaxs(-1, -1);
mv.visitEnd();
cw.visitEnd();

lookup = lookup.defineHiddenClass(
cw.toByteArray(), true, MethodHandles.Lookup.ClassOption.NESTMATE);
return lookup.findStatic(lookup.lookupClass(), "get", type);
}
这将生成一个带有 static 的新类包含 tableswitch 的方法指令和调用(以及我们现在必须自己做的装箱转换)。此外,它还具有为越界值创建和抛出异常所需的代码。生成类后,它返回一个句柄 static方法。

关于jvm - 如何使用 `tableswitch` 模拟 `MethodHandle` ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66947934/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com