`MethodHandle` 访问 Object 时比 Reflection 慢

问题描述 投票:0回答:2

我想以尽可能高效的方式通过反射调用方法。

该方法返回一个对象。

我使用反射和 MethodHandles 实现了这一点,我期望 MethodHandle 更快 - 但这不是我所看到的(大约慢 20-40%)。

采用以下 JMH 基准:

import org.openjdk.jmh.annotations.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 200, time = 10, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class AccessorBenchmark {
    private static final Object[] EMPTY_ARGS = new Object[0];

    private POJO source;
    private Method method;
    private MethodHandle methodHandle;
    private MethodHandle methodHandleModifiedReturnType;

    @Setup
    public void setup() throws ReflectiveOperationException {
        source = new POJO();

        method = source.getClass().getDeclaredMethod("getNumber");
        
        methodHandle = MethodHandles.lookup().unreflect(method);
        methodHandleModifiedReturnType = methodHandle.asType(methodHandle.type().changeReturnType(Number.class));
    }

    @Benchmark
    public Number reflection() throws Throwable {
        return (Number) method.invoke(source, EMPTY_ARGS);
    }

    @Benchmark
    public Number methodHandle() throws Throwable {
        return (Number) methodHandle.invoke(source);
    }

    @Benchmark
    public Number methodHandleInvokeExact() throws Throwable {
        return  (Number) methodHandleModifiedReturnType.invokeExact(source);
    }

    public class POJO {
        private final AtomicInteger counter = new AtomicInteger();

        public AtomicInteger getNumber() {
            return counter;
        }
    }
}

Java 17 返回以下结果:

Benchmark                                     Mode   Cnt   Score    Error   Units
AccessorBenchmark.methodHandle                avgt  1000   2.856 ±  0.004   ns/op
AccessorBenchmark.methodHandleInvokeExact     avgt  1000   2.359 ±  0.003   ns/op
AccessorBenchmark.reflection                  avgt  1000   2.017 ±  0.002   ns/op

有什么想法吗?

java performance reflection methodhandle
2个回答
1
投票

您需要使您的方法句柄

static final
,以便它们可以不断折叠(显然)。

请参阅这篇文章 Java Reflection,但速度更快,它根据您的需要探讨了反射与方法句柄性能。


0
投票

您已经使用反射来确定方法句柄,而您本来可以在不使用反射的情况下查找句柄。为直接调用和直接查找方法句柄添加额外的基准,无需反射,如下:

/** Test POJO directly, no reflection / lookup indirection */
@Benchmark
public Number direct() throws Throwable {
    return source.getNumber();
}
private static final Lookup     LOOKUP = MethodHandles.lookup();
/** Lookup details of an instance method handle on a Java class */
static MethodHandle findInstanceMH(Class<?> cls, String callback, MethodType methodType) {
    try {
        return LOOKUP.findVirtual(cls, callback, methodType);
    } catch (NoSuchMethodException | IllegalAccessException e) {
        throw new Error("Not found callback: "+callback);
    }
}
private static final MethodHandle MH  = findInstanceMH(POJO.class, "getNumber", MethodType.methodType(AtomicInteger.class));
private static final MethodHandle MHN = MH.asType(MH.type().changeReturnType(Number.class));

/** Test MethodHandle obtained by lookup, not reflection */
@Benchmark
public Number methodHandleNormal() throws Throwable {
    return  (Number) MHN.invokeExact(source);
}

使用新的基准测试,您应该会看到方法处理版本比反射快得多,并且与直接调用相比效果很好:

Benchmark                                  Mode   Cnt   Score   Error  Units
AccessorBenchmark.direct                   avgt  1000   4.925 ± 0.047  ns/op
AccessorBenchmark.methodHandle             avgt  1000  10.327 ± 0.066  ns/op
AccessorBenchmark.methodHandleInvokeExact  avgt  1000   9.478 ± 0.066  ns/op
AccessorBenchmark.methodHandleNormal       avgt  1000   5.063 ± 0.054  ns/op
AccessorBenchmark.reflection               avgt  1000  17.477 ± 0.116  ns/op
© www.soinside.com 2019 - 2024. All rights reserved.