问题
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)
回答1:
Functionally MethodHandleProxies::asInterfaceInstance seems like a good fit. Not sure about the performance impact of the method handle proxies though.
回答2:
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.
来源:https://stackoverflow.com/questions/50835353/create-biconsumer-as-field-setter-without-reflection