更快地替代Java的反思 [英] Faster alternatives to Java's reflection

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

问题描述

正如我们所知,反射是一种灵活但方法,用于在运行时维护和修改代码的行为。

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

但如果我们必须使用这样的功能,与Reflection 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?

推荐答案

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

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.

但更重要的是,Reflection会对每次调用进行安全检查。所以你生成的类只会在加载和实例化时检查,这可能是一个很大的胜利。但是,您也可以在方法实例上调用 setAccessible(true)来打开安全检查。然后,只剩下自动装箱和varargs阵列创建的轻微性能损失。

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. But alternatively you can invoke setAccessible(true) on a Method instance to turn the security checks of. 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.

Update
使用 Java 8 ,可以选择在运行时使用lambda表达式的后端和方法引用语言功能。这个后端完全符合开头所描述的内容,生成一个动态类,它实现了一个接口你的代码可以在编译时知道它直接调用。确切的机制是特定于实现的,因此是未定义的,但您可以假设实现将尽可能快地进行调用。 Oracle JRE的当前实现完美无缺。这不仅可以节省您生成这样一个访问者类的负担,而且还可以执行您从未做过的事情 - 通过生成的代码调用甚至私有方法。我已更新示例以包含此解决方案。此示例使用已存在且恰好具有所需方法签名的标准接口。如果不存在这样的匹配接口,则必须使用具有正确签名的方法创建自己的访问者功能接口。但是,当然,现在示例代码需要运行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设置中打印的旧程序:直接:0,03s,mh:0,32s,反射:1,05s 这表明 MethodHandle 是个不错的选择。现在,在同一台机器上运行Java 8的更新程序打印直接:0,02s,lambda:0,02s,mh:0,35s,反射: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天全站免登陆