I was investigating if replacing a java.lang.reflect.Field.get
call with a VarHandle
would improve performance, but instead it got slower.
From benchmarking, I can see that when the VarHandle
is static
, it outperforms Field.get
(obviously) - but when it's not, it's about twice as slow.
For my use-case, it needs to generic across unknown classes - so not static - e.g. think about serialization.
Is there an alternative way to outperform Field.get
?
Benchmark:
import org.openjdk.jmh.annotations.*;
import java.lang.invoke.*;
import java.lang.reflect.Field;
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 ReflectiveFieldAccessBenchmark {
static {
try {
field = ReflectiveFieldAccessBenchmark.class.getDeclaredField("count");
} catch (final ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
private static final Field field;
private static final VarHandle staticVarHandle = getVarHandle();
private final VarHandle nonStaticVarHandle = getVarHandle();
private final Number count = new AtomicInteger(Integer.MAX_VALUE);
private static VarHandle getVarHandle() {
try {
return MethodHandles.lookup().unreflectVarHandle(field);
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Benchmark
public Object reflection() throws Exception {
return field.get(this);
}
@Benchmark
public Object staticVarHandle() throws Exception {
return staticVarHandle.get(this);
}
@Benchmark
public Object nonStaticVarHandle() throws Exception {
return nonStaticVarHandle.get(this);
}
}
Output:
Benchmark Mode Cnt Score Error Units
ReflectiveFieldAccessBenchmark.nonStaticVarHandle avgt 1000 3.485 ± 0.005 ns/op
ReflectiveFieldAccessBenchmark.reflection avgt 1000 2.027 ± 0.002 ns/op
ReflectiveFieldAccessBenchmark.staticVarHandle avgt 1000 0.433 ± 0.002 ns/op
Is there an alternative way to outperform Field.get?
The Lmbda library
ToIntFunction<ReflectiveFieldAccessBenchmark> lambdaFactory = LambdaFactory.create(new LambdaType<ToIntFunction<ReflectiveFieldAccessBenchmark>>() {
}, MethodHandles.lookup().unreflectGetter(field));
lambdaFactoryBoxed.apply(this);
Implementation | ns/op |
---|---|
Direct | 0.32 |
Reflection | 1.92 |
Lmbda | 0.68 |