我构建了一个简单的测试,用Java LambdaMetafactory测量类反射的性能。根据各种帖子,使用LambdaMetafactory的反射与直接调用getter一样快。这似乎最初是正确的,但一段时间后性能下降。
其中一个测试节目(这似乎是一般趋势):
最初:
GET - REFLECTION: Runtime=2.841seconds
GET - DIRECT: Runtime=0.358seconds
GET - LAMBDA: Runtime=0.362seconds
SET - REFLECTION: Runtime=3.86seconds
SET - DIRECT: Runtime=0.507seconds
SET - LAMBDA: Runtime=0.455seconds
最后:
GET - REFLECTION: Runtime=2.904seconds
GET - DIRECT: Runtime=0.501seconds
GET - LAMBDA: Runtime=5.299seconds
SET - REFLECTION: Runtime=4.62seconds
SET - DIRECT: Runtime=1.723seconds
SET - LAMBDA: Runtime=5.149seconds
代码如下。
问题:
为什么?
如何通过LambdaMetafactory提高效率?
package lambda;
public class Main {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, Throwable {
int runs = 5;
for (int i = 0; i < runs; i++) {
System.out.println("***** RUN " + i);
Lambda lam = new Lambda();
lam.initGetter(Person.class, "getName");
lam.initSetter(Person.class, "setSalary", double.class);
Test test = new Test();
test.doTest(lam);
}
}
}
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.function.BiConsumer;
import java.util.function.Function;
public final class Lambda {
//https://www.optaplanner.org/blog/2018/01/09/JavaReflectionButMuchFaster.html
//https://bytex.solutions/2017/07/java-lambdas/
//https://stackoverflow.com/questions/27602758/java-access-bean-methods-with-lambdametafactory
private Function getterFunction;
private BiConsumer 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 void initGetter(Class theSubject, String methodName) throws ReflectiveOperationException, Exception {
MethodHandles.Lookup lookup = MethodHandles.lookup();
Method m = theSubject.getMethod(methodName);
MethodHandle mh = lookup.unreflect(m);
getterFunction = createGetter(lookup, mh);
}
public void initSetter(Class theSubject, String methodName, Class parameterType) throws ReflectiveOperationException, Exception {
MethodHandles.Lookup lookup = MethodHandles.lookup();
Method m = theSubject.getMethod(methodName, parameterType);
MethodHandle mh = lookup.unreflect(m);
setterFunction = createSetter(lookup, mh);
}
public Object executeGetter(Object theObject) {
return getterFunction.apply(theObject);
}
public void executeSetter(Object theObject, Object value) {
setterFunction.accept(theObject, value);
}
}
package lambda;
import java.lang.reflect.Field;
public class Test {
public void doTest(Lambda lam) throws NoSuchFieldException, IllegalArgumentException, Exception {
if (lam == null) {
lam = new Lambda();
lam.initGetter(Person.class, "getName");
lam.initSetter(Person.class, "setSalary", double.class);
}
Person p = new Person();
p.setName(111);
long loops = 1000000000; // 10e9
System.out.println("Loops: " + loops);
///
System.out.println("GET - REFLECTION:");
Field field = Person.class.getField("name");
long start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
int name = (int) field.get(p);
}
long end = System.currentTimeMillis();
System.out.println("Runtime=" + ((end - start) / 1000.0) + "seconds");
///
System.out.println("GET - DIRECT:");
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
int name = (int) p.getName();
}
end = System.currentTimeMillis();
System.out.println("Runtime=" + ((end - start) / 1000.0) + "seconds");
////
System.out.println("GET - LAMBDA:");
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
int name = (int) lam.executeGetter(p);
}
end = System.currentTimeMillis();
System.out.println("Runtime=" + ((end - start) / 1000.0) + "seconds");
///
System.out.println("SET - REFLECTION:");
int name = 12;
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
field.set(p, name);
}
end = System.currentTimeMillis();
System.out.println("Runtime=" + ((end - start) / 1000.0) + "seconds");
///
System.out.println("SET - DIRECT:");
name = 33;
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
p.setName(name);
}
end = System.currentTimeMillis();
System.out.println("Runtime=" + ((end - start) / 1000.0) + "seconds");
////
System.out.println("SET - LAMBDA:");
Double name2 = 2.3;
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
lam.executeSetter(p, name2);
}
end = System.currentTimeMillis();
System.out.println("Runtime=" + ((end - start) / 1000.0) + "seconds");
}
}
package lambda;
public class Person {
public int name;
private double salary;
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
正如后面的评论中所述,我已经更新了代码,以便作为一个独立的测试运行,我可以从我的应用程序的任何一点调用它。测试接受类,方法名称(字符串)和类实例以运行性能测试。正如预期的那样,当我只使用一个类(Person1)时,性能很好。一旦我为两个类调用另一个类(Person2)的性能下降测试 - 有时性能甚至比普通方法反射调用更差。下面是一些数据。最初,Person1的表现还可以。一旦使用了Person2,就会有一个下降。
(Class=Person1, name=Name)
Loops: 50000000
GET - DIRECT: Runtime=0.016seconds
GET - LAMBDA: Runtime=0.016seconds
(Class=Person2, name=Name)
Loops: 50000000
GET - DIRECT: Runtime=0.019seconds
createLambda: new getter for clazz=lambda.Person2 name=Name
createLambda: new setter for clazz=lambda.Person2 name=Name
GET - LAMBDA: Runtime=0.069seconds
(Class=Person1, name=Name)
Loops: 50000000
GET - DIRECT: Runtime=0.02seconds
GET - LAMBDA: Runtime=0.045seconds
(Class=Person2, name=Name)
Loops: 50000000
GET - DIRECT: Runtime=0.017seconds
GET - LAMBDA: Runtime=0.054seconds
(Class=Person1, name=Name)
Loops: 50000000
GET - DIRECT: Runtime=0.018seconds
GET - LAMBDA: Runtime=0.045seconds