我想以尽可能高效的方式通过反射调用方法。
该方法返回一个对象。
我使用反射和 MethodHandles 实现了这一点,我期望 MethodHandle 更快 - 但这不是我所看到的(大约慢 20-40%)。
采用以下 JMH 基准:
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;
}
}
}
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
有什么想法吗?
您已经使用反射来确定方法句柄,而您本来可以在不使用反射的情况下查找句柄。为直接调用和直接查找方法句柄添加额外的基准,无需反射,如下:
/** Test POJO directly, no reflection / lookup indirection */
@Benchmark
public Number direct() throws Throwable {
return source.getNumber();
}
private static final Lookup LOOKUP = MethodHandles.lookup();
/** Lookup details of an instance method handle on a Java class */
static MethodHandle findInstanceMH(Class<?> cls, String callback, MethodType methodType) {
try {
return LOOKUP.findVirtual(cls, callback, methodType);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new Error("Not found callback: "+callback);
}
}
private static final MethodHandle MH = findInstanceMH(POJO.class, "getNumber", MethodType.methodType(AtomicInteger.class));
private static final MethodHandle MHN = MH.asType(MH.type().changeReturnType(Number.class));
/** Test MethodHandle obtained by lookup, not reflection */
@Benchmark
public Number methodHandleNormal() throws Throwable {
return (Number) MHN.invokeExact(source);
}
使用新的基准测试,您应该会看到方法处理版本比反射快得多,并且与直接调用相比效果很好:
Benchmark Mode Cnt Score Error Units
AccessorBenchmark.direct avgt 1000 4.925 ± 0.047 ns/op
AccessorBenchmark.methodHandle avgt 1000 10.327 ± 0.066 ns/op
AccessorBenchmark.methodHandleInvokeExact avgt 1000 9.478 ± 0.066 ns/op
AccessorBenchmark.methodHandleNormal avgt 1000 5.063 ± 0.054 ns/op
AccessorBenchmark.reflection avgt 1000 17.477 ± 0.116 ns/op