为何LambdaMetafactory性能比Reflection差?JMH测试结果异常
我从《Faster alternatives to Java's reflection》一文了解到,结合MethodHandle使用LambdaMetafactory的调用性能接近直接调用,但在我的JMH测试中,LambdaMetafactory的性能反而比Reflection更差,与预期情况不符,不清楚问题出在哪里。
JMH测试结果(AverageTime模式)
Benchmark Mode Cnt Score Error Units MhExceptionBenchMark.MhExceptioTest.directNew avgt 10 248.059 ± 52.375 ns/op MhExceptionBenchMark.MhExceptioTest.mhLamda avgt 10 2435.962 ± 45.767 ns/op MhExceptionBenchMark.MhExceptioTest.mhNoLamda avgt 10 2573.739 ± 688.203 ns/op MhExceptionBenchMark.MhExceptioTest.reflet avgt 10 2344.597 ± 237.946 ns/op
目标类定义
ValidException类
public class ValidException extends AbstractException { private static final long serialVersionUID = 1L; private static final String DEFAULT_VALID_ERRCODE = "test"; public ValidException() { super(DEFAULT_VALID_ERRCODE); } public ValidException(String errMessage) { super(DEFAULT_VALID_ERRCODE, errMessage); } }
AbstractException父类
public abstract class AbstractException extends RuntimeException { private static final long serialVersionUID = 1L; private String errMessage; public AbstractException(String errMessage) { super(errMessage); } }
JMH测试代码
@Fork(1) @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 3) @Measurement(iterations = 10, time = 5) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Threads(10) public class MhExceptioTest { @State(Scope.Benchmark) public static class Ex { ValidException validException; @Setup(Level.Trial) public void prepare() { validException = new ValidException(); } } @State(Scope.Benchmark) public static class MhNoLambda{ MethodHandle methodHandle; @Setup(Level.Trial) public void prepare() throws NoSuchMethodException, IllegalAccessException { MethodType methodType = MethodType.methodType(void.class, String.class); methodHandle = MethodHandles.publicLookup().findConstructor(ValidException.class, methodType); } } @State(Scope.Benchmark) public static class MhLambda{ Function<String,AbstractException> function; @Setup(Level.Trial) public void prepare() throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodType methodType = MethodType.methodType(void.class, String.class); MethodHandle methodHandle = lookup.findConstructor(ValidException.class, methodType); CallSite site = LambdaMetafactory.metafactory( lookup, "apply", MethodType.methodType(Function.class), methodHandle.type().generic(), methodHandle, methodHandle.type()); function = (Function<String, AbstractException>)site.getTarget().invokeExact(); } } @State(Scope.Benchmark) public static class Constr{ Constructor<ValidException> constructor; @Setup(Level.Trial) public void prepare() throws NoSuchMethodException { constructor = ValidException.class.getConstructor(String.class); } } @Benchmark public <T extends AbstractException> AbstractException directNew(Ex ex){ ValidException validException = ex.validException; validException.setErrMessage("test"); return validException; } @Benchmark public <T extends AbstractException> AbstractException mhNoLamda(MhNoLambda mhNoLambda) throws Throwable { return (AbstractException) mhNoLambda.methodHandle.invoke("test"); } @Benchmark public <T extends AbstractException> AbstractException reflet(Constr constr) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { ValidException validException = constr.constructor.newInstance("test"); return validException; } @Benchmark public <T extends AbstractException> AbstractException mhLamda(MhLambda mhLambda){ return mhLambda.function.apply("test"); } public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder() .include(MhExceptioTest.class.getSimpleName()) .result("./result-mh-ex.json") .resultFormat(ResultFormatType.JSON) .build(); new Runner(options).run(); } }
问题原因分析
1. directNew基准测试逻辑无效
directNew方法并没有创建新的ValidException实例,只是复用了@Setup阶段初始化好的单个实例,仅修改其errMessage属性。而其他三个测试方法(mhLamda、mhNoLamda、reflet)都是每次调用都创建新实例,两者核心逻辑完全不同,因此directNew的性能结果不具备参考性,不能用来对比动态创建实例的性能。
2. MethodHandle调用未使用最优方式
mhNoLamda中使用methodHandle.invoke("test"),而invoke方法会进行类型适配和自动装箱/拆箱,带来额外开销。应该使用invokeExact来避免这些开销,修改为:
return (AbstractException) mhNoLambda.methodHandle.invokeExact("test");
3. LambdaMetafactory的潜在优化点
虽然你的LambdaMetafactory使用逻辑大体正确,但可以调整参数以确保生成的Lambda实现更高效:
- 显式指定
samMethodType参数:Function接口的apply方法泛型擦除后的类型是MethodType.methodType(Object.class, Object.class),你当前使用的methodHandle.type().generic()等价于这个类型,但显式指定可避免潜在类型匹配问题:MethodType samType = MethodType.methodType(Object.class, Object.class); CallSite site = LambdaMetafactory.metafactory( lookup, "apply", MethodType.methodType(Function.class), samType, methodHandle, methodHandle.type()); - 增加预热迭代次数:当前
@Warmup(iterations = 3)可能不足以让JVM完成Lambda的优化,尝试将预热次数提升至5次,给JVM足够时间生成优化后的字节码。
4. 现代JVM对反射的优化
现代JVM(如OpenJDK 8+)对反射调用有显著优化,当反射方法被多次调用后,JVM会生成动态字节码替代原生反射调用,缩小了与MethodHandle的性能差距。但LambdaMetafactory生成的Lambda理论上应该能超过反射,需修正测试问题后重新验证。
5. 线程数带来的干扰
你使用了@Threads(10),多线程环境下对象创建的内存分配竞争可能影响性能测试结果。建议先使用单线程(@Threads(1))测试,排除线程竞争干扰后再观察性能差异。
内容的提问来源于stack exchange,提问作者benym

