`MethodHandle` 比反射慢

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

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

该方法返回一个原语

long

我使用反射和

MethodHandle
s 来实现这一点。

我期待

MethodHandle
会更快,因为:

  • 这是
    MethodHandle
    s
  • 的好处之一
  • 当您体验反射时,它可以避免装箱/拆箱

但在我的所有基准测试中,

MethodHandle
速度较慢(约 2-5%)。

采用以下 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.AtomicLong;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class AccessorBenchmark {
    private POJO source;

    private ReflectionAccessor reflectionAccessor;
    private MethodHandleAccessor methodHandleAccessor;

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

        final Method method = source.getClass().getDeclaredMethod("longMethod");

        reflectionAccessor = new ReflectionAccessor(method);
        methodHandleAccessor = new MethodHandleAccessor(method);
    }

    @Benchmark
    public long reflectionAccessor() throws ReflectiveOperationException {
        return reflectionAccessor.get(source);
    }

    @Benchmark
    public long methodHandleAccessor() throws Throwable {
        return methodHandleAccessor.get(source);
    }

    public class ReflectionAccessor {
        private final Object[] EMPTY_ARGS = new Object[0];
        private final Method method;

        public ReflectionAccessor(final Method method) {
            this.method = method;
        }

        public long get(final Object source) throws ReflectiveOperationException {
            return ((Number) method.invoke(source, EMPTY_ARGS)).longValue();
        }
    }

    public class MethodHandleAccessor {
        private MethodHandle methodHandle;

        public MethodHandleAccessor(final Method method) throws ReflectiveOperationException {
            methodHandle = MethodHandles.lookup().unreflect(method);
            methodHandle = methodHandle
                    .asType(methodHandle.type().changeReturnType(long.class).changeParameterType(0, Object.class));
        }

        public long get(final Object source) throws Throwable {
            return (long) methodHandle.invokeExact(source);
        }
    }

    public class POJO {
        // Chose a value outside of the autoboxing cache range
        private final AtomicLong counter = new AtomicLong(Long.MAX_VALUE);

        /** Some dummy method that returns different values in consistent amounts of time */
        public long longMethod() {
            return counter.addAndGet(-1);
        }
    }
}

Java 17 返回以下结果:

Benchmark                                Mode  Cnt     Score    Error   Units
AccessorBenchmark.methodHandleAccessor   avgt   25     4.204 ±  0.546   ns/op
AccessorBenchmark.reflectionAccessor     avgt   25     4.123 ±  0.040   ns/op

有什么想法吗?

java performance reflection methodhandle
1个回答
0
投票

仔细查看结果中的错误列:

Score    Error   Units
4.204 ±  0.546   ns/op

当测量误差高达13%时,得出2-5%性能差异的结论是不正确的。

高误差值的原因显然是缺乏适当的预热:

@Warmup(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)

1 次 100 毫秒的迭代不足以让基准达到稳定状态。至少运行 5 次每次 1 秒的预热迭代,并注意每次测量的结果,以确保它们在迭代之间不会有太大变化。

这就是相同的基准(经过适当的预热)在我的笔记本电脑上显示的结果。这里的 MethodHandles 预计会更快:

Benchmark                               Mode  Cnt  Score   Error  Units
AccessorBenchmark.methodHandleAccessor  avgt   30  5.946 ± 0.054  ns/op
AccessorBenchmark.reflectionAccessor    avgt   30  6.379 ± 0.069  ns/op
© www.soinside.com 2019 - 2024. All rights reserved.