简体   繁体   English

如何使用`MethodHandle`模仿`tableswitch`?

[英]How to mimic `tableswitch` using `MethodHandle`?

Context: I've been benchmarking the difference between using invokedynamic and manually generating bytecode (this is in the context of deciding whether a compiler targeting the JVM should emit more verbose "traditional" bytecode or just an invokedynamic call with a clever bootstrap method).上下文:我一直在对使用invokedynamic和手动生成字节码之间的区别进行基准测试(这是在决定针对 JVM 的编译器是否应该发出更详细的“传统”字节码或只是使用巧妙的引导方法的invokedynamic调用的上下文中)。 In doing this, it has been pretty straightforward to map bytecode into MethodHandles combinators that are at least as fast, with the exception of tableswitch .在执行此操作时,将 map 字节码转换为至少一样快的MethodHandles组合器非常简单,除了tableswitch

Question: Is there a trick to mimic tableswitch using MethodHandle ?问题:有没有使用MethodHandle模拟tableswitch的技巧? I tried mimicking it with a jump table: using a constant MethodHandle[] , indexing into that with arrayElementGetter , then calling the found handle with MethodHandles.invoker .我尝试使用跳转表来模仿它:使用常量MethodHandle[] ,使用MethodHandles.invoker对其进行索引,然后使用arrayElementGetter调用找到的句柄。 However, that ended up being around 50% slower than the original bytecode when I ran it through JMH.然而,当我通过 JMH 运行它时,它最终比原始字节码慢了大约 50%。

Here's the code for producing the method handle:下面是生成方法句柄的代码:

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);
}

Here's the initial bytecode (which I'm trying to replace with one invokedynamic call)这是初始字节码(我试图用一个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

The good thing about invokedynamic is that it allows to postpone the decision, how to implement the operation to the actual runtime. invokedynamic的好处是它允许推迟决策,即如何将操作实现到实际运行时。 This is the trick behind LambdaMetafactory or StringConcatFactory which may return composed method handles, like in your example code, or dynamically generated code, at the particular implementation's discretion.这是LambdaMetafactoryStringConcatFactory背后的技巧,它可能会返回组合方法句柄,如您的示例代码或动态生成的代码,由特定实现自行决定。

There's even a combined approach possible, generate classes which you compose to an operation, eg settling on the already existing LambdaMetafactory :甚至还有一种可能的组合方法,生成您组合到操作中的类,例如解决已经存在的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));
}

This uses the LambdaMetafactory to generate a Function instance for each getter, similar to equivalent method references.这使用LambdaMetafactory为每个 getter 生成Function实例,类似于等效的方法引用。 Then, an actual class calling the right Function 's apply method is instantiated and a method handle to its get method returned.然后,调用正确的Functionapply方法的实际 class 被实例化,并返回其get方法的方法句柄。

This is a similar composition as your method handles, but with the reference implementation, no handles but fully materialized classes are used.这是与您的方法句柄类似的组合,但在参考实现中,不使用句柄,而是使用完全物化的类。 I'd expect the composed handles and this approach to converge to the same performance for a very large number of invocations, but the materialized classes having a headstart for a medium number of invocations.我希望组合句柄和这种方法能够在大量调用中收敛到相同的性能,但物化类在中等数量的调用中具有领先优势。

I added a first parameter MethodHandles.Lookup lookup which should be the lookup object received by the bootstrap method for the invokedynamic instruction.我添加了第一个参数MethodHandles.Lookup lookup ,它应该是调用动态指令的引导方法收到的lookup invokedynamic If used that way, the generated functions can access all methods the same way as the code containing the invokedynamic instruction, including private methods of that class.如果以这种方式使用,生成的函数可以以与包含invokedynamic指令的代码相同的方式访问所有方法,包括该 class 的private方法。

Alternatively, you can generate a class containing a real switch instruction yourself.或者,您可以自己生成包含真实开关指令的 class。 Using the ASM library , it may look like:使用ASM 库,它可能看起来像:

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);
}

This generates a new class with a static method containing the tableswitch instruction and the invocations (as well as the boxing conversions we now have to do ourselves).这将生成一个新的 class 和static方法,其中包含tableswitch指令和调用(以及我们现在必须自己进行的装箱转换)。 Also, it has the necessary code to create and throw an exception for out-of-bounds values.此外,它还具有必要的代码来为越界值创建和抛出异常。 After generating the class, it returns a handle to that static method.生成 class 后,它返回该static方法的句柄。

I don't know of your timeline.我不知道你的时间表。 But it is likely there will be a MethodHandles.tableSwitch operation in Java 17. It is currently being integrated via https://github.com/openjdk/jdk/pull/3401/但是Java 17中很可能会有MethodHandles.tableSwitch操作。目前正在通过https进行集成://github.com/openjdk/jdk/pull/3401/

Some more discussion about it here: https://mail.openjdk.java.net/pipermail/core-libs-dev/2021-April/076105.html更多关于它的讨论: https://mail.openjdk.java.net/pipermail/core-libs-dev/2021-April/076105.html

The things is, tableswitch isn't always compiled to a jump table .事情是, tableswitch 并不总是编译为 jump table For a small number of labels, like in your example, it's likely to act as a binary search.对于少量标签,例如在您的示例中,它可能充当二进制搜索。 Thus using a tree of regular "if-then" MethodHandles will be the closest equivalent.因此,使用常规的“if-then” MethodHandles 树将是最接近的等价物。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM