vendredi 20 avril 2018

Why does Java lamda reflection performce get worse

I have constructed a simple test to measure the performance of class reflection with the Java LambdaMetafactory. According to various posts relfection using LambdaMetafactory is as fast as directly calling getters. This seems true initially, but after a while performance degrades.

One of the test shows (this seems general trend):

Initially:

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

Finally:

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

Code follows below.

Questions:

  • Why?

  • How can this be made more performant with the 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;
    }

}





Aucun commentaire:

Enregistrer un commentaire