dimanche 13 mai 2018

Java reflection performance - alternatives

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)


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);
        }
    }
}





Aucun commentaire:

Enregistrer un commentaire