我想以尽可能高效的方式通过反射调用方法。
该方法返回一个原语
long
。
我使用反射和
MethodHandle
s 来实现这一点。
我期待
MethodHandle
会更快,因为:
MethodHandle
s但在我的所有基准测试中,
MethodHandle
速度较慢(约 2-5%)。
采用以下 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.AtomicLong;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class AccessorBenchmark {
private POJO source;
private ReflectionAccessor reflectionAccessor;
private MethodHandleAccessor methodHandleAccessor;
@Setup
public void setup() throws ReflectiveOperationException {
source = new POJO();
final Method method = source.getClass().getDeclaredMethod("longMethod");
reflectionAccessor = new ReflectionAccessor(method);
methodHandleAccessor = new MethodHandleAccessor(method);
}
@Benchmark
public long reflectionAccessor() throws ReflectiveOperationException {
return reflectionAccessor.get(source);
}
@Benchmark
public long methodHandleAccessor() throws Throwable {
return methodHandleAccessor.get(source);
}
public class ReflectionAccessor {
private final Object[] EMPTY_ARGS = new Object[0];
private final Method method;
public ReflectionAccessor(final Method method) {
this.method = method;
}
public long get(final Object source) throws ReflectiveOperationException {
return ((Number) method.invoke(source, EMPTY_ARGS)).longValue();
}
}
public class MethodHandleAccessor {
private MethodHandle methodHandle;
public MethodHandleAccessor(final Method method) throws ReflectiveOperationException {
methodHandle = MethodHandles.lookup().unreflect(method);
methodHandle = methodHandle
.asType(methodHandle.type().changeReturnType(long.class).changeParameterType(0, Object.class));
}
public long get(final Object source) throws Throwable {
return (long) methodHandle.invokeExact(source);
}
}
public class POJO {
// Chose a value outside of the autoboxing cache range
private final AtomicLong counter = new AtomicLong(Long.MAX_VALUE);
/** Some dummy method that returns different values in consistent amounts of time */
public long longMethod() {
return counter.addAndGet(-1);
}
}
}
Java 17 返回以下结果:
Benchmark Mode Cnt Score Error Units
AccessorBenchmark.methodHandleAccessor avgt 25 4.204 ± 0.546 ns/op
AccessorBenchmark.reflectionAccessor avgt 25 4.123 ± 0.040 ns/op
有什么想法吗?
仔细查看结果中的错误列:
Score Error Units
4.204 ± 0.546 ns/op
当测量误差高达13%时,得出2-5%性能差异的结论是不正确的。
高误差值的原因显然是缺乏适当的预热:
@Warmup(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)
1 次 100 毫秒的迭代不足以让基准达到稳定状态。至少运行 5 次每次 1 秒的预热迭代,并注意每次测量的结果,以确保它们在迭代之间不会有太大变化。
这就是相同的基准(经过适当的预热)在我的笔记本电脑上显示的结果。这里的 MethodHandles 预计会更快:
Benchmark Mode Cnt Score Error Units
AccessorBenchmark.methodHandleAccessor avgt 30 5.946 ± 0.054 ns/op
AccessorBenchmark.reflectionAccessor avgt 30 6.379 ± 0.069 ns/op