Тема обсуждалась в разных вопросах ( Ссылка-1 , Ссылка-2 ).Но почему я вижу производительность намного хуже для лямбда-отражения?Часто утверждается, что лямбда-отражение так же быстро, как прямой доступ.Но тест ниже показывает другие результаты.Здесь полевые отражения кажутся почти такими же быстрыми, как и лямбда-отражения.
Для повторения теста: исходный код следует ниже.
Вывод теста:
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)
Использование JMH(для кода, см. ниже) результаты для одного теста пробега:
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)
Где
# 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 код:
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;
}
}