Java反射性能 - 替代

时间:2018-05-13 22:22:53

标签: java performance reflection lambda

在各种问题(Reference-1Reference-2)中讨论了主题。但为什么我看到lambda反射表现更差?通常声称lambda反射与直接访问一样快。但是下面的测试显示了其他结果。这里的场反射几乎和λ反射一样快。

重复测试:源代码如下。

测试结果:

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)

使用JMH(代码见下文)单个fork运行基准测试的结果是:

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)

其中

# 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代码:

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 个答案:

没有答案