为什么Java lamda反射性能会变差

时间:2018-04-20 15:48:25

标签: java performance reflection lambda

我构建了一个简单的测试,用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

0 个答案:

没有答案