I would like to call a method via reflection in the most performant way possible.
The method returns an Object.
I've implemented this using both reflection and MethodHandles, I was expecting MethodHandle to be faster - but that's not what I'm seeing (~20-40% slower).
Take the following JMH benchmark:
import org.openjdk.jmh.annotations.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 200, time = 10, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class AccessorBenchmark {
private static final Object[] EMPTY_ARGS = new Object[0];
private POJO source;
private Method method;
private MethodHandle methodHandle;
private MethodHandle methodHandleModifiedReturnType;
@Setup
public void setup() throws ReflectiveOperationException {
source = new POJO();
method = source.getClass().getDeclaredMethod("getNumber");
methodHandle = MethodHandles.lookup().unreflect(method);
methodHandleModifiedReturnType = methodHandle.asType(methodHandle.type().changeReturnType(Number.class));
}
@Benchmark
public Number reflection() throws Throwable {
return (Number) method.invoke(source, EMPTY_ARGS);
}
@Benchmark
public Number methodHandle() throws Throwable {
return (Number) methodHandle.invoke(source);
}
@Benchmark
public Number methodHandleInvokeExact() throws Throwable {
return (Number) methodHandleModifiedReturnType.invokeExact(source);
}
public class POJO {
private final AtomicInteger counter = new AtomicInteger();
public AtomicInteger getNumber() {
return counter;
}
}
}
The following result is returned with Java 17:
Benchmark Mode Cnt Score Error Units
AccessorBenchmark.methodHandle avgt 1000 2.856 ± 0.004 ns/op
AccessorBenchmark.methodHandleInvokeExact avgt 1000 2.359 ± 0.003 ns/op
AccessorBenchmark.reflection avgt 1000 2.017 ± 0.002 ns/op
Any ideas?
You need to make your method handles
static finalso that they can be constant folded (apparently).See this article Java Reflection, but much faster, which explores reflection vs. method handle performance as you want.