Java reflection performance - alternatives

2019-05-20 02:42发布

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)

Using JMH (for code, see below) the results for a single fork run benchmark are:

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)

Where

# 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 code:

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

}

0条回答
登录 后发表回答