Topic discussed in various questions (Reference-1, Reference-2). But why do I see a performance much worse for lambda reflection? Often it is claimed that lambda reflection is as fast as direct access. But the test below shows other results. Here field reflections seems to be almost as fast as lambda reflection.
To repeat test: source code follows further below.
Output of test:
run:
> Test: Main (Class=Person1, name=Age)
> RUN 0
Loops: 50000000
Warmup
createLambda: new getter for clazz=lambda.Person1 name=Age
createLambda: new setter for clazz=lambda.Person1 name=Age
Run
GET FIELD=0.17s METHOD=0.66s DIRECT=0.03s LAMBDA=0.18s
SET FIELD=0.29s METHOD=0.68s DIRECT=0.03s LAMBDA=0.15s
> RUN 1
Loops: 50000000
Warmup
Run
GET FIELD=0.19s METHOD=0.41s DIRECT=0.02s LAMBDA=0.14s
SET FIELD=0.22s METHOD=0.64s DIRECT=0.04s LAMBDA=0.14s
> Test: Main (Class=Person2, name=Age)
> RUN 0
Loops: 50000000
Warmup
createLambda: new getter for clazz=lambda.Person2 name=Age
createLambda: new setter for clazz=lambda.Person2 name=Age
Run
GET FIELD=0.17s METHOD=0.42s DIRECT=0.02s LAMBDA=0.17s
SET FIELD=0.23s METHOD=0.65s DIRECT=0.04s LAMBDA=0.18s
> RUN 1
Loops: 50000000
Warmup
Run
GET FIELD=0.17s METHOD=0.42s DIRECT=0.02s LAMBDA=0.16s
SET FIELD=0.23s METHOD=0.68s DIRECT=0.05s LAMBDA=0.19s
> Test: Main (Class=Person3, name=Age)
> RUN 0
Loops: 50000000
Warmup
createLambda: new getter for clazz=lambda.Person3 name=Age
createLambda: new setter for clazz=lambda.Person3 name=Age
Run
GET FIELD=0.15s METHOD=0.39s DIRECT=0.02s LAMBDA=0.28s
SET FIELD=0.23s METHOD=0.62s DIRECT=0.03s LAMBDA=0.29s
> RUN 1
Loops: 50000000
Warmup
Run
GET FIELD=0.16s METHOD=0.40s DIRECT=0.02s LAMBDA=0.28s
SET FIELD=0.23s METHOD=0.62s DIRECT=0.04s LAMBDA=0.29s
> Test: Main (Class=Person1, name=Age)
> RUN 0
Loops: 50000000
Warmup
Run
GET FIELD=0.17s METHOD=0.41s DIRECT=0.02s LAMBDA=0.32s
SET FIELD=0.24s METHOD=0.62s DIRECT=0.04s LAMBDA=0.31s
> RUN 1
Loops: 50000000
Warmup
Run
GET FIELD=0.16s METHOD=0.42s DIRECT=0.02s LAMBDA=0.24s
SET FIELD=0.23s METHOD=0.59s DIRECT=0.03s LAMBDA=0.24s
BUILD SUCCESSFUL (total time: 32 seconds)
Using JMH (for code, see below) the results for a single fork run benchmark are:
Benchmark Mode Cnt Score Error Units
Test.doGetTest1 avgt 5 6,133 ± 0,578 ns/op (FIELD)
Test.doGetTest2 avgt 5 7,947 ± 0,970 ns/op (METHOD)
Test.doGetTest3 avgt 5 2,294 ± 0,140 ns/op (DIRECT)
Test.doGetTest4 avgt 5 3,802 ± 0,479 ns/op (LAMBDA)
Test.doGetTest5 avgt 5 5,347 ± 0,530 ns/op (METHODHANDLE)
Test.doSetTest1 avgt 5 8,727 ± 0,918 ns/op (FIELD)
Test.doSetTest2 avgt 5 7,760 ± 0,382 ns/op (METHOD)
Test.doSetTest3 avgt 5 2,448 ± 0,255 ns/op (DIRECT)
Test.doSetTest4 avgt 5 4,230 ± 0,160 ns/op (LAMBDA)
Test.doSetTest5 avgt 5 5,729 ± 0,576 ns/op (METHODHANDLE)
Where
# JMH version: 1.21
# VM version: JDK 1.8.0_172, Java HotSpot(TM) 64-Bit Server VM, 25.172-b11
# VM invoker: C:\Program Files\Java\jre1.8.0_172\bin\java.exe
# VM options: <none>
package lambda;
public class Main {
public static void main(String[] args) throws Exception {
int runs = 2;
lambda.Test.doTestRuns("Main", runs, Person1.class, "Age", new Person1());
lambda.Test.doTestRuns("Main", runs, Person2.class, "Age", new Person2());
lambda.Test.doTestRuns("Main", runs, Person3.class, "Age", new Person3());
lambda.Test.doTestRuns("Main", runs, Person1.class, "Age", new Person1());
}
}
package lambda;
public class Person1 {
public long Age = 0;
public long getAge() {
return Age;
}
public void setAge(long age) {
this.Age = age;
}
}
package lambda;
public class Person2 {
public long Age = 0;
public long getAge() {
return Age;
}
public void setAge(long age) {
this.Age = age;
}
}
package lambda;
public class Person3 {
public long Age = 0;
public long getAge() {
return Age;
}
public void setAge(long age) {
this.Age = age;
}
}
package lambda;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import lambda.LambdaFactory.Lambda;
public class Test {
long loops = 50_000_000;
static LambdaFactory lamFactory = new LambdaFactory();
public static void doTestRuns(String label, int runs, Class clazz, String name, Object o) throws Exception {
System.out.println("> Test: " + label + " (Class=" + clazz.getSimpleName() + ", name=" + name + ")");
Test test = new Test();
for (int i = 0; i < runs; i++) {
System.out.println("> RUN " + i);
test.doTest(clazz, name, o);
}
}
public void doTest(Class clazz, String name, Object o) throws NoSuchFieldException, IllegalArgumentException, Exception {
final long[] dummy=new long[8];
System.out.println("Loops: " + loops);
// needed for Direct method tests
Person1 p = new Person1();
System.out.println("Warmup");
dummy[0] += doGetTest1(dummy[0], clazz, name, o);
dummy[1] += doGetTest2(dummy[1], clazz, name, o);
dummy[2] += doGetTest3(dummy[2], p);
dummy[3] += doGetTest4(dummy[3], clazz, name, o);
dummy[4] += doSetTest1(dummy[4], clazz, name, o);
dummy[5] += doSetTest2(dummy[5], clazz, name, o);
dummy[6] += doSetTest3(dummy[6], p);
dummy[7] += doSetTest4(dummy[7], clazz, name, o);
System.out.println("Run");
long t0 = System.nanoTime();
dummy[0] += doGetTest1(dummy[0], clazz, name, o);
long t1 = System.nanoTime();
dummy[1] += doGetTest2(dummy[1], clazz, name, o);
long t2 = System.nanoTime();
dummy[2] += doGetTest3(dummy[2], p);
long t3 = System.nanoTime();
dummy[3] += doGetTest4(dummy[3], clazz, name, o);
long t4 = System.nanoTime();
dummy[4] += doSetTest1(dummy[4], clazz, name, o);
long t5 = System.nanoTime();
dummy[5] += doSetTest2(dummy[5], clazz, name, o);
long t6 = System.nanoTime();
dummy[6] += doSetTest3(dummy[6], p);
long t7 = System.nanoTime();
dummy[7] += doSetTest4(dummy[7], clazz, name, o);
long t8 = System.nanoTime();
System.out.printf("GET FIELD=%.2fs METHOD=%.2fs DIRECT=%.2fs LAMBDA=%.2fs\n",
((t1 - t0) / 1000000000.0),
((t2 - t1) / 1000000000.0),
((t3 - t2) / 1000000000.0),
((t4 - t3) / 1000000000.0));
System.out.printf("SET FIELD=%.2fs METHOD=%.2fs DIRECT=%.2fs LAMBDA=%.2fs\n",
((t5 - t4) / 1000000000.0),
((t6 - t5) / 1000000000.0),
((t7 - t6) / 1000000000.0),
((t8 - t7) / 1000000000.0));
}
public long doGetTest1(long v, Class clazz, String name, Object o) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field field = clazz.getField(name);
for (int i = 0; i < loops; i++) {
v += (long)field.get(o);
}
return v;
}
public long doGetTest2(long v, Class clazz, String name, Object o) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method method = clazz.getMethod("get" + name);
for (int i = 0; i < loops; i++) {
v += (long)method.invoke(o);
}
return v;
}
public long doGetTest3(long v, Person1 p) {
for (int i = 0; i < loops; i++) {
v += (long) p.getAge();
}
return v;
}
public Long doGetTest4(long v, Class clazz, String name, Object o) throws Exception {
Lambda lam = lamFactory.createLambda(clazz, name);
for (int i = 0; i < loops; i++) {
v += (long) lam.get(o);
}
return v;
}
public long doSetTest1(long v, Class clazz, String name, Object o) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
long value = 1;
Field field = clazz.getField(name);
for (int i = 0; i < loops; i++) {
field.set(o, value);
value++;
}
return v + value;
}
public long doSetTest2(long v, Class clazz, String name, Object o) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
long value = 1;
Method getter = clazz.getMethod("get" + name);
Method method = clazz.getMethod("set" + name, getter.getReturnType());
for (int i = 0; i < loops; i++) {
method.invoke(o, value);
value++;
}
return v + value;
}
public long doSetTest3(long v, Person1 p) {
long value = 1;
for (int i = 0; i < loops; i++) {
p.setAge(value);
value++;
}
return v + value;
}
public long doSetTest4(long v, Class clazz, String name, Object o) throws Exception {
Lambda lam = lamFactory.createLambda(clazz, name);
long value = 1;
for (int i = 0; i < loops; i++) {
lam.set(o, value);
value++;
}
return v + value;
}
}
package lambda;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
public class LambdaFactory {
// create a cache of Functions and BiConsumers
public HashMap<Method, Function> getters = new HashMap();
public HashMap<Method, BiConsumer> setters = new HashMap();
// get the lookup factory once
MethodHandles.Lookup lookup = MethodHandles.lookup();
public Lambda createLambda(Class clazz, String name) throws Exception {
Method getterMethod = clazz.getMethod("get" + name);
Function getterFunction = getters.get(getterMethod);
if (getterFunction == null) {
MethodHandle mh = lookup.unreflect(getterMethod);
getterFunction = createGetter(lookup, mh);
getters.put(getterMethod, getterFunction);
System.out.println("createLambda: new getter for clazz=" + clazz.getName() + " name=" + name);
}
Method setterMethod = clazz.getMethod("set" + name, getterMethod.getReturnType());
BiConsumer setterFunction = setters.get(setterMethod);
if (setterFunction == null) {
MethodHandle mh = lookup.unreflect(setterMethod);
setterFunction = createSetter(lookup, mh);
setters.put(setterMethod, setterFunction);
System.out.println("createLambda: new setter for clazz=" + clazz.getName() + " name=" + name);
}
return new Lambda(getterFunction, setterFunction);
}
private Function createGetter(final MethodHandles.Lookup lookup,
final MethodHandle getter) throws Exception {
final CallSite site = LambdaMetafactory.metafactory(lookup, "apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class), //signature of method Function.apply after type erasure
getter,
getter.type()); //actual signature of getter
try {
return (Function) site.getTarget().invokeExact();
} catch (final Exception e) {
throw e;
} catch (final Throwable e) {
throw new Exception(e);
}
}
private BiConsumer createSetter(final MethodHandles.Lookup lookup,
final MethodHandle setter) throws Exception {
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
try {
return (BiConsumer) site.getTarget().invokeExact();
} catch (final Exception e) {
throw e;
} catch (final Throwable e) {
throw new Exception(e);
}
}
public class Lambda {
private final Function getterFunction;
private final BiConsumer setterFunction;
public Lambda(Function getterFunction, BiConsumer setterFunction) {
this.getterFunction = getterFunction;
this.setterFunction = setterFunction;
}
public Object get(Object theObject) {
return getterFunction.apply(theObject);
}
public void set(Object theObject, Object value) {
setterFunction.accept(theObject, value);
}
}
}
JMH code:
package lambda;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lambda.LambdaFactory.Lambda;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
public class Test {
@State(Scope.Thread)
public static class MyState {
LambdaFactory lamFactory;
Lambda lam;
Field field;
Method getterMethod;
Method setterMethod;
MethodHandle getterHandle;
MethodHandle setterHandle;
Person1 object;
long value;
@Setup(Level.Trial)
public void doSetup() throws Exception {
lamFactory = new LambdaFactory();
lam = lamFactory.createLambda(Person1.class, "Age");
field = Person1.class.getField("Age");
getterMethod = Person1.class.getMethod("getAge");
setterMethod = Person1.class.getMethod("setAge", long.class);
MethodHandles.Lookup lookup = MethodHandles.lookup();
getterHandle = lookup.unreflect(getterMethod);
setterHandle = lookup.unreflect(setterMethod);
object = new Person1();
value = 123;
System.out.println("Do Setup");
}
@TearDown(Level.Trial)
public void doTearDown() {
System.out.println("Do TearDown");
}
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doGetTest1(MyState state) throws IllegalArgumentException, IllegalAccessException {
long v = (long) state.field.get(state.object);
return v;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doGetTest2(MyState state) throws IllegalAccessException, InvocationTargetException {
long v = (long) state.getterMethod.invoke(state.object);
return v;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doGetTest3(MyState state) throws IllegalAccessException, InvocationTargetException {
long v = state.object.getAge();
return v;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doGetTest4(MyState state) throws IllegalAccessException, InvocationTargetException {
long v = (long) state.lam.get(state.object);
return v;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doGetTest5(MyState state) throws IllegalAccessException, InvocationTargetException, Throwable {
long v = (long) state.getterHandle.invoke(state.object);
return v;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doSetTest1(MyState state) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
state.field.set(state.object, state.value);
return state.value;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doSetTest2(MyState state) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
state.setterMethod.invoke(state.object, state.value);
return state.value;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doSetTest3(MyState state) {
state.object.setAge(state.value);
return state.value;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doSetTest4(MyState state) throws Exception {
state.lam.set(state.object, state.value);
return state.value;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long doSetTest5(MyState state) throws Exception, Throwable {
state.setterHandle.invoke(state.object, state.value);
return state.value;
}
}