2013-10-24 166 views
45

正如我们所知,反射是一种灵活的方法,在运行时维护和修改代码的行为。Java反射的更快替代方案

但是,如果我们必须使用这样的功能,那么Java中与Reflection API相比动态修改有更快的编程技术吗?这些备选方案的反思与反思有何优缺点?

+8

主要的选择是_not_在运行时不操纵和修改代码,这通常是相当快的。 –

+4

一个相当棘手的选择是直接操作字节码,请参阅https://code.google.com/p/java-box-factory/,http://asm.ow2.org/,http://commons.apache .org/proper/commons-bcel/ – harpun

+3

你在做什么反思,目前的性能影响是什么? –

回答

99

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

但更重要的是,Reflection会对每个调用执行安全检查。所以你的生成的类将在加载和实例化时进行检查,这可能是一个巨大的胜利。但是,您也可以调用setAccessible(true)上的Method实例来打开安全检查。然后,只有自动装箱和可变参数阵列创建的小性能损失仍然存在。

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

更新) 随着爪哇8,存在使用lambda表达式和方法参考语言特性的后端在运行时的选项。这个后端完成了刚开始描述的事情,动态生成一个类,它实现了一个代码可以在编译时知道的直接调用的代码。确切的机制是特定于实现的,因此未定义,但您可以假定实现将尝试尽可能快地进行调用。目前Oracle的JRE实现完美。这不仅可以让您免除生成这样的访问器类的负担,还可以执行您永远无法做到的事情 - 通过生成的代码调用即使方法也不例外。我已经更新了示例以包含此解决方案。此示例使用已存在的标准interface,并恰好具有所需的方法签名。如果不存在这种匹配interface,则必须使用具有正确签名的方法创建您自己的访问器功能接口。但是,当然,现在示例代码需要运行Java 8。

下面是一个简单的基准例如:

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方法的能力。

一如既往,我必须指出这个基准的简单性以及它是多么的人为。但我认为,这种趋势清晰可见,甚至更重要,结果令人信服地可以解释。

+3

+1提供的比较 – maasg

+0

希望方法句柄在Java 8中获得更快的速度。 – Ingo

+2

使用目前的测试版,它们不是。但是,那是'beta'。而且它们将被lambda实现所使用,所以改进它有更多的压力。 – Holger

7

反射的替代方法是使用接口。从Effective Java by Joshua Bloch.

只是走,而仅在非常有限的形式使用它承担其成本的少数 我们可以得到很多的反射带来的好处。对于必须使用在编译时不可用的类的许多 程序, 在编译时存在一个合适的接口或超类 来引用该类。如果是这种情况,您可以创建 实例并通过它们的接口或超类访问它们。如果相应的构造函数没有参数,那么你甚至不需要使用java.lang.reflect; Class.newInstance方法 提供了所需的功能。仅用于创建对象即

// Reflective instantiation with interface access 
    public static void main(String[] args) { 
     // Translate the class name into a Class object 
     Class<?> cl = null; 
     try { 
      cl = Class.forName(args[0]); 
     } catch(ClassNotFoundException e) { 
      System.err.println("Class not found."); 
      System.exit(1); 
     } 
     // Instantiate the class 
     Set<String> s = null; 
     try { 
      s = (Set<String>) cl.newInstance(); 
     } catch(IllegalAccessException e) { 
      System.err.println("Class not accessible."); 
      System.exit(1); 
     } catch(InstantiationException e) { 
      System.err.println("Class not instantiable."); 
      System.exit(1); 
     } 
     // Exercise the set 
     s.addAll(Arrays.asList(args).subList(1, args.length)); 
     System.out.println(s); 
} 

虽然这个方案仅仅是一个玩具

使用反射,它表明该技术是 非常强大。该玩具程序可以很容易地变成一个通用的 套件测试器,该套件测试器通过 积极操纵一个或多个实例并检查它们是否遵守Set合约来验证指定的Set实现。同样,它可以变成通用的 集性能分析工具。事实上,这项技术足以实现一个全面的服务提供商框架。当时大多数是 ,这种技术就是你所需要的所有方式,反映在 。

这个例子说明了反射的两个缺点。首先, 示例可能会生成三个运行时错误,如果未使用反射实例化,所有这些错误都会导致编译时错误。其次,需要20行冗长的代码来从其名称生成类的实例 ,而构造函数调用将在单行上整齐地匹配。然而,这些缺点仅限于实例化对象的程序部分。一旦实例化,它就与任何其他Set 实例无法区分。

+0

-Ve投票请评论。 – Trying

11

我创建了一个名为lambda-factory的小型图书馆。它基于LambdaMetafactory,但可以节省寻找或创建与方法匹配的接口的麻烦。

下面是10E8迭代一些样品运行时(可重放与类PerformanceTest):

LAMBDA:0.02S,直接:0.01S,反射:4.64s用于方法(INT,INT)
LAMBDA:0.03 S,直销:0.02秒,反思:3.23s的方法(对象,INT)

比方说,我们有一个叫做MyClass类,它定义了以下方法:

private static String myStaticMethod(int a, Integer b){ /*some logic*/ } 
private float myInstanceMethod(String a, Boolean b){ /*some logic*/ } 

我们可以访问这些甲基ODS是这样的:

Method method = MyClass.class.getDeclaredMethod("myStaticMethod", int.class, Integer.class); //Regular reflection call 
Lambda lambda = LambdaFactory.create(method); 
String result = (String) lambda.invoke_for_Object(1000, (Integer) 565); //Don't rely on auto boxing of arguments! 

Method method = MyClass.class.getDeclaredMethod("myInstanceMethod", String.class, Boolean.class); 
Lambda lambda = LambdaFactory.create(method); 
float result = lambda.invoke_for_float(new MyClass(), "Hello", (Boolean) null); //No need to cast primitive results! 

注意,调用拉姆达时,你必须选择一个包含在其名称中的目标方法的返回类型的调用方法。 - 可变参数和汽车拳击太贵了。

在上面的例子中,所选的invoke_for_float方法表明我们正在调用一个返回float的方法。如果你试图访问的方法返回一个String,一个盒装原语(Integer,Boolean等)或者一些自定义对象,你可以调用invoke_for_Object

该项目是与LambdaMetafactory尝试,因为它包含了各方面的工作代码一个很好的模板:

  1. 静态调用和实例调用
  2. 访问私有方法,并从其他包中的方法
  3. 'invokeSpecial'逻辑,即创建的实现是这样的,它绕过动态方法分派。
+0

检查了您的代码,无法编译您的代码,Lamba - 无法解析为类型。 –

+0

@aryanRaj_kary它适用于我。你提出的问题在引用的github项目的“从源代码构建”的段落下回答。小结:您必须A)运行“mvn clean install”并且B)指示您的IDE包含生成的源文件。或者简单得多,你只需下载jar文件并使用它(点击'发布'选项卡) – Hervian

+0

尝试了jar,但得到这个异常:java.lang.NoSuchFieldException:allowedModes Method method = TestSACP.class.getDeclaredMethod “getData”,String.class); Lambda lambda = LambdaFactory.create(method); String result =(String)lambda.invoke_for_Object(new TestSACP(),“hello”); 公共类TestSACP { 私人字符串的getData(字符串一){ \t \t回报 “从丰顺” + A; \t}} 冉 –