通过反射在Java中调用getter:重复调用它的最快方法是什么(性能和可伸缩性明智)?

2022-09-02 23:31:49

给定一个类和一个属性,我在编译时都不知道,我需要多次重复调用 getter。FoobarFoo.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的最快方法是什么?


答案 1

您可以使用方法句柄。它的Javadoc写道:

使用查找 API 中的工厂方法,可以将由核心反射 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 具有一些不同的性能特征,因此,只有在您可以将 MethodHandle 放在静态最终字段中时才会有所帮助,在这种情况下,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

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


答案 2

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

如果我运行,请使用 getter 方法,无论是否具有可访问 true。

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) 可以快三倍。


推荐