1

I try to get the maximum performance in one of my scripts, without doing a major refactor.

I spotted method that creates a BiConsumer from a Field using reflection.

return (c, v) -> {
    try {
        field.set(c, v);
    } catch (final Throwable e) {
        throw new RuntimeException("Could not set field: " + field, e);
    }
};

Reflection has the reputation of being slow. So I though I could use the method handles.

Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.unreflectSetter(field);
return (c, v) -> {
    try {
        mh.invoke(c, v);
    } catch (final Throwable e) {
        throw new RuntimeException("Could not set field: " + field, e);
    }
};

Which is already a tiny bit faster. However BiConsumer is a FunctionalInterface which could just be generated somehow.

public static <C, V> BiConsumer<C, V> createSetter(final MethodHandles.Lookup lookup,
        final Field field) throws Exception {
    final MethodHandle setter = lookup.unreflectSetter(field);
    final CallSite site = LambdaMetafactory.metafactory(lookup,
            "accept",
            MethodType.methodType(BiConsumer.class),
            MethodType.methodType(void.class, Object.class, Object.class), // signature of method BiConsumer.accept after type erasure
            setter,
            setter.type()); // actual signature of setter
    return (BiConsumer<C, V>) site.getTarget().invokeExact();
}

However then I get an Exception which I don't really understand

Exception in thread "main" java.lang.invoke.LambdaConversionException: Unsupported MethodHandle kind: putField org.sample.dto.TestDTO.name:(String)void
    at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:182)
    at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
    at org.sample.bench.MyBenchmark.createSetter(MyBenchmark.java:120)
    at org.sample.bench.MyBenchmark.main(MyBenchmark.java:114)

In which way do I have to generate that setter correctly to increase the performance. (without actually adding a setter method)

ST-DDT
  • 2,615
  • 3
  • 30
  • 51
  • 1
    Like the exception says; you can't wrap a putField method handle using `LambdaMetafactory`. Your first solution is the best alternative imho. – Jorn Vernee Jun 13 '18 at 11:11
  • @Jorn Vernee are you referring to the reflection way or the plain method handle? – ST-DDT Jun 13 '18 at 12:34
  • The plain method handle way, where you wrap it in a lambda expression. – Jorn Vernee Jun 13 '18 at 12:56
  • Did you benchmark this? or you just think it is slow? – GotoFinal Jun 25 '18 at 09:19
  • Both the reflection and method handles are insanely fast after a few executions (if you cache them). However MethodHandles are ~15% faster. Thats why I made this little tradeof. ~1 line of code for 15% performance improvement. If it is executed >>1000 times then it becomes worth it. (Reflection 1, MethodHandle by name 0.9, MethodHandle by Reflection 0.85) – ST-DDT Jun 26 '18 at 09:50

2 Answers2

5

You can use an invoker MethodHandle:

public static <C, V> BiConsumer<C, V> createSetter(
                     MethodHandles.Lookup lookup, Field field) throws Throwable {
    final MethodHandle setter = lookup.unreflectSetter(field);
    MethodType type = setter.type();
    if(field.getType().isPrimitive())
        type = type.wrap().changeReturnType(void.class);
    final CallSite site = LambdaMetafactory.metafactory(lookup,
        "accept", MethodType.methodType(BiConsumer.class, MethodHandle.class),
        type.erase(), MethodHandles.exactInvoker(setter.type()), type);
    return (BiConsumer<C, V>)site.getTarget().invokeExact(setter);
}

Since the LambdaMetafactory does not allow to generate a function instance for the field method handle, the code above creates a function instance for a method handle equivalent to invoking invokeExact on the field accessor method handle.

In the worst case, the generated code would not differ from the code generated for the lambda expression performing a manual invocation of the method handle’s invoke method, so the performance would be on par with

public static <C, V> BiConsumer<C, V> createSetterU(
                     MethodHandles.Lookup lookup, Field field) throws Throwable {
    MethodHandle setter = lookup.unreflectSetter(field);
    return (c, v) -> {
        try {
            setter.invoke(c, v);
        } catch (final Throwable e) {
            throw new RuntimeException("Could not set field: " + field, e);
        }
    };
}

However, there are some chances that the invoker method handle is slightly more efficient in some scenarios. First, it uses the equivalent to invokeExact rather than invoke, an option, the generic code can’t use due to type erasure. Second, there might be some internal tweaks when no user code is involved in the code graph.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • When Im Trying to use this sollution I'm having Exception in thread "main" java.lang.AbstractMethodError: Receiver class org.home.ref.App$$Lambda$15/0x0000000800098040 does not define or inherit an implementation of the resolved method abstract accept(Ljava/lang/Object;Ljava/lang/Object;)V of interface java.util.function.BiConsumer. – Kristoff May 04 '20 at 12:30
  • 1
    @Kristoff do you use a field with a primitive type? – Holger May 04 '20 at 13:05
  • yeah, its boolean. – Kristoff May 04 '20 at 13:06
  • Yeah issue is gone, but it seems that executing accept method on this references has no effect. Would you mind lookign at https://stackoverflow.com/questions/61592566/create-biconsumer-from-lambdametafactory I have more details there – Kristoff May 04 '20 at 13:18
2

Functionally MethodHandleProxies::asInterfaceInstance seems like a good fit. Not sure about the performance impact of the method handle proxies though.

Stanislav Lukyanov
  • 2,147
  • 10
  • 20