17

给定一个类Foo和一个属性bar我在编译时都不知道,我需要多次多次调用 getter Foo.getBar()

假设我有:

Method barGetterMethod = ...; // Don't worry how I got this

我需要做这样的事情:

for (Object foo : fooList) { // 1000000000 elements in fooList
    Object bar = barGetterMethod.invoke(foo);
    ...
}

与在没有反射的情况下调用它相比,上面的实现仍然非常慢。有更快的方法吗?

在 Java 中使用反射调用 getter 的最快方法是什么?

4

3 回答 3

31

您可能会使用MethodHandle。它的 Javadoc 写道:

使用 Lookup API 中的工厂方法,由 Core Reflection API 对象表示的任何类成员都可以转换为行为等效的方法句柄。例如,可以使用 Lookup.unreflect 将反射方法转换为方法句柄。生成的方法句柄通常提供对底层类成员的更直接和有效的访问。

虽然这将减少开销,但如果使用通常的(非反射性)字节码指令进行调用,方法句柄仍会阻止 JVM 可以采用的某些优化(例如方法内联)。这种优化是否有益取决于您如何使用该方法(如果该代码路径总是调用相同的方法,则内联会有所帮助,如果每次都是不同的方法,则可能不会)。

以下微基准测试可能会让您大致了解反射、方法句柄和直接调用的相对性能:

package tools.bench;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.math.BigDecimal;

public abstract class Bench {

    final String name;

    public Bench(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1; 
            } while (duration < 100000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }   

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    static class C {
        public Integer foo() {
            return 1;
        }
    }

    static final MethodHandle sfmh;

    static {
        try {
            Method m = C.class.getMethod("foo");
            sfmh = MethodHandles.lookup().unreflect(m);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        final C invocationTarget = new C();
        final Method m = C.class.getMethod("foo");
        final Method am = C.class.getMethod("foo");
        am.setAccessible(true);
        final MethodHandle mh = sfmh;

        Bench[] marks = {
            new Bench("reflective invocation (without setAccessible)") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x += (Integer) m.invoke(invocationTarget);
                    }
                    return x;
                }
            },
            new Bench("reflective invocation (with setAccessible)") {                   
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x += (Integer) am.invoke(invocationTarget);
                    }
                    return x;
                }
            },
            new Bench("methodhandle invocation") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x += (Integer) mh.invokeExact(invocationTarget);
                    }
                    return x;
                }
            },
            new Bench("static final methodhandle invocation") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x += (Integer) sfmh.invokeExact(invocationTarget);
                    }
                    return x;
                }
            },
            new Bench("direct invocation") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x += invocationTarget.foo();
                    }
                    return x;
                }
            },
        };
        for (Bench bm : marks) {
            System.out.println(bm);
        }
    }
}

在我有点过时的笔记本上

java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) Client VM (build 22.0-b10, mixed mode, sharing)

这打印:

reflective invocation (without setAccessible)   568.506 ns
reflective invocation (with setAccessible)  42.377 ns
methodhandle invocation 27.461 ns
static final methodhandle invocation    9.402 ns
direct invocation   9.363 ns

更新:正如 Irreputable 指出的那样,服务器 VM 的性能特征有些不同,因此在服务器 VM 中使用 MethodHandle 只会有帮助,前提是您可以将它放在静态 final 字段中,在这种情况下,VM 可以内联调用:

reflective invocation (without setAccessible)   9.736 ns
reflective invocation (with setAccessible)  7.113 ns
methodhandle invocation 26.319 ns
static final methodhandle invocation    0.045 ns
direct invocation   0.044 ns

我建议您衡量您的特定用例。

于 2013-01-03T20:41:57.660 回答
11

呼叫barReadMethod.setAccessible(true);会关闭安全检查,这可以使其更快一些。即使它是可访问的,它也必须另外检查。

如果我运行使用带有和不带有可访问 true 的 getter 方法。

class Main {
    static class A {
        private final Integer i;

        A(Integer i) {
            this.i = i;
        }

        public Integer getI() {
            return i;
        }
    }

    public static void main(String... args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        A[] as = new A[100000];
        for (int i = 0; i < as.length; i++)
            as[i] = new A(i);

        for (int i = 0; i < 5; i++) {
            long time1 = timeSetAccessible(as);
            long time2 = timeNotSetAccessible(as);
            System.out.printf("With setAccessible true %.1f ns, Without setAccessible %.1f ns%n",
                   (double) time1 / as.length, (double) time2 / as.length);
        }
    }

    static long dontOptimiseAvay = 0;

    private static long timeSetAccessible(A[] as) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Method getter = A.class.getDeclaredMethod("getI");
        getter.setAccessible(true);
        dontOptimiseAvay = 0;
        long start = System.nanoTime();
        for (A a : as) {
            dontOptimiseAvay += (Integer) getter.invoke(a);
        }
        return System.nanoTime() - start;
    }

    private static long timeNotSetAccessible(A[] as) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Method getter = A.class.getDeclaredMethod("getI");
//        getter.setAccessible(true);
        dontOptimiseAvay = 0;
        long start = System.nanoTime();
        for (A a : as) {
            dontOptimiseAvay += (Integer) getter.invoke(a);
        }
        return System.nanoTime() - start;
    }
}

印刷

With setAccessible true 106.4 ns, Without setAccessible 126.9 ns
With setAccessible true 5.4 ns, Without setAccessible 29.4 ns
With setAccessible true 3.2 ns, Without setAccessible 9.9 ns
With setAccessible true 3.1 ns, Without setAccessible 9.0 ns
With setAccessible true 3.1 ns, Without setAccessible 8.9 ns

对于简单的 getter,使用 setAccessible(true) 可以快三倍。

于 2013-01-03T20:25:27.260 回答
1

如果上面讨论的静态最终 MethodHandle 选项不实用/不可能,另一种选择是使用bytebuddy动态生成一个类,该类有一个采用 foo 的方法,在 foo 上调用 bar 方法并返回结果。

这将为 bar 的单个调用提供与直接调用基本相同的性能(包装的调用最终可能是内联的)。

但是,这会产生 1 次为类和方法生成字节码的成本。根据 bytebuddy 网站,此成本约为 200ns。

于 2016-05-31T18:04:38.573 回答