Java 反射的更快替代方案 [英] Faster alternatives to Java's reflection

查看:46
本文介绍了Java 反射的更快替代方案的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

众所周知,反射是一种灵活但缓慢的方法,用于在运行时维护和修改代码的行为.

As we know, reflection is a flexible but slow method to maintain and modify the behaviour of the code at runtime.

但是如果我们必须使用这样的功能,与动态修改的反射 API 相比,Java 中有没有更快的编程技术?这些针对反射的替代方案的优缺点是什么?

But if we have to use such a functionality, are there any faster programming techniques in Java compared to Reflection API for dynamic modifications? What are pros and cons of these alternatives against reflection?

推荐答案

反射的一种替代方法是动态生成类文件.这个生成的类应该执行所需的操作,例如调用在运行时发现的方法,并实现一个在编译时已知的 interface,以便可以使用该接口以非反射方式调用生成的方法.有一个问题:如果适用,反射在内部执行相同的技巧.这在特殊情况下不起作用,例如调用 private 方法时,因为您无法生成调用它的合法类文件.所以在反射实现中有不同类型的调用处理程序,使用生成的代码或本机代码.你无法打败它.

One alternative to Reflection is to generate a class file dynamically. This generated class ought to perform the desired action, e.g. invokes the method discovered at runtime, and implements an interface known at compile-time so that it’s possible to invoke the generated method in a non-reflective way using that interface. There’s one catch: if applicable, Reflection does the same trick internally. This does not work in special cases, e.g. when invoking a private method as you can’t generate a legal class file invoking it. So in the Reflection implementation there are different types of invocation handlers, using either generated code or native code. You can’t beat that.

但更重要的是,反射会对每次调用进行安全检查.因此,您生成的类将仅在加载和实例化时进行检查,这可能是一个巨大的胜利.或者,您可以在 Method 实例上调用 setAccessible(true) 以关闭安全检查.那么只有自动装箱和可变参数数组创建的轻微性能损失仍然存在.

But more important is that Reflection does security checks on every invocation. So your generated class will be checked on loading and instantiation only which can be a big win. Alternatively you can invoke setAccessible(true) on a Method instance to turn the security checks off. Then only the minor performance loss of autoboxing and varargs array creation remains.

Java 7 起,就可以使用 MethodHandle 替代两者.最大的优势在于,与其他两个不同,它甚至可以在安全受限的环境中工作.MethodHandle 的访问检查在获取它时执行,而不是在调用它时执行.它具有所谓的多态签名",这意味着您可以使用任意参数类型调用它,而无需自动装箱或创建数组.当然,错误的参数类型会创建一个合适的RuntimeException.

Since Java 7 there is an alternative to both, the MethodHandle. The big advantage is that, unlike the other two, it even works in security restricted environments. The access checks for a MethodHandle are performed when acquiring it but not when invoking it. It has the so-called "polymorphic signature" which means you can invoke it with arbitrary argument types without auto-boxing nor array creation. Of course, wrong argument types will create an appropriate RuntimeException.

(更新)对于 Java 8,可以选择在运行时使用 lambda 表达式和方法引用语言功能的后端.这个后端完全按照开头描述的那样做,动态生成一个类,它实现一个 interface 您的代码可以在编译时已知时直接调用.确切的机制是特定于实现的,因此未定义,但您可以假设实现将尽最大努力使调用尽可能快.Oracle 的 JRE 的当前实现完美地做到了这一点.这不仅使您免于生成这样一个访问器类的负担,它还能够做您永远做不到的事情——甚至通过生成的代码调用 private 方法.我已更新示例以包含此解决方案.这个例子使用了一个标准的interface,它已经存在并且恰好具有所需的方法签名.如果不存在这样匹配的interface,则必须使用具有正确签名的方法创建自己的访问器功能接口.但是,当然,现在示例代码需要运行 Java 8.

(Update) With Java 8, there is the option to use the back-end of the lambda expression and method reference language feature at runtime. This backend does exactly the thing described at the beginning, generating a class dynamically which implements an interface your code may call directly when it is known at compile-time. The exact mechanics is implementation-specific, hence undefined, but you can assume that the implementation will try it’s best to make the invocation as fast as possible. The current implementation of Oracle’s JRE does it perfectly. Not only that this saves you from the burden of generating such an accessor class, it is also capable of doing what you never could do— invoke even private methods via generated code. I have updated the example to include this solution. This example uses a standard interface which already exists and happens to have the desired method signature. If no such matching interface exists, you have to create your own accessor functional interface with a method with the right signature. But, of course, now the example code requires Java 8 to run.

这是一个简单的基准示例:

Here is a simple benchmark example:

import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.function.IntBinaryOperator;

public class TestMethodPerf
{
  private static final int ITERATIONS = 50_000_000;
  private static final int WARM_UP = 10;

  public static void main(String... args) throws Throwable
  {
 // hold result to prevent too much optimizations
    final int[] dummy=new int[4];

    Method reflected=TestMethodPerf.class
      .getDeclaredMethod("myMethod", int.class, int.class);
    final MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh=lookup.unreflect(reflected);
    IntBinaryOperator lambda=(IntBinaryOperator)LambdaMetafactory.metafactory(
      lookup, "applyAsInt", MethodType.methodType(IntBinaryOperator.class),
      mh.type(), mh, mh.type()).getTarget().invokeExact();

    for(int i=0; i<WARM_UP; i++)
    {
      dummy[0]+=testDirect(dummy[0]);
      dummy[1]+=testLambda(dummy[1], lambda);
      dummy[2]+=testMH(dummy[1], mh);
      dummy[3]+=testReflection(dummy[2], reflected);
    }
    long t0=System.nanoTime();
    dummy[0]+=testDirect(dummy[0]);
    long t1=System.nanoTime();
    dummy[1]+=testLambda(dummy[1], lambda);
    long t2=System.nanoTime();
    dummy[2]+=testMH(dummy[1], mh);
    long t3=System.nanoTime();
    dummy[3]+=testReflection(dummy[2], reflected);
    long t4=System.nanoTime();
    System.out.printf("direct: %.2fs, lambda: %.2fs, mh: %.2fs, reflection: %.2fs%n",
      (t1-t0)*1e-9, (t2-t1)*1e-9, (t3-t2)*1e-9, (t4-t3)*1e-9);

    // do something with the results
    if(dummy[0]!=dummy[1] || dummy[0]!=dummy[2] || dummy[0]!=dummy[3])
      throw new AssertionError();
  }

  private static int testMH(int v, MethodHandle mh) throws Throwable
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=(int)mh.invokeExact(1000, v);
    return v;
  }

  private static int testReflection(int v, Method mh) throws Throwable
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=(int)mh.invoke(null, 1000, v);
    return v;
  }

  private static int testDirect(int v)
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=myMethod(1000, v);
    return v;
  }

  private static int testLambda(int v, IntBinaryOperator accessor)
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=accessor.applyAsInt(1000, v);
    return v;
  }

  private static int myMethod(int a, int b)
  {
    return a<b? a: b;
  }
}

在我的 Java 7 设置中打印的旧程序:direct: 0,03s, mh: 0,32s,reflection: 1,05s 这表明 MethodHandle 是一个不错的选择.现在,在同一台机器上在 Java 8 下运行的更新程序打印 direct: 0,02s, lambda: 0,02s, mh: 0,35s,reflection: 0,40s 这清楚地表明了反射性能已经改进到可能使处理 MethodHandle 变得不必要的程度,除非您使用它来执行 lambda 技巧,这显然优于所有反射替代方案,这并不奇怪,因为它只是一个直接的调用(好吧,几乎:一级间接).请注意,我将目标方法设为 private 以演示甚至有效调用 private 方法的能力.

Th old program printed in my Java 7 setup: direct: 0,03s, mh: 0,32s, reflection: 1,05s which suggested that MethodHandle was a good alternative. Now, the updated program running under Java 8 on the same machine printed direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40s which clearly shows that Reflection performance has been improved to a degree that might make dealing with MethodHandle unnecessary, unless you use it to do the lambda trick, that clearly outperforms all reflective alternatives, which comes at no surprise, as it is just a direct call (well, almost: one level of indirection). Note that I made the target method private to demonstrate the capability of calling even private methods efficiently.

和往常一样,我必须指出这个基准测试的简单性以及它是多么的人为.但我认为,这种趋势是显而易见的,更重要的是,结果是可以令人信服地解释的.

As always, I have to point at the simplicity of this benchmark and how artificial it is. But I think, the tendency is clearly visible and even more important, the results are convincingly explainable.

这篇关于Java 反射的更快替代方案的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆