java 反射 速度_Java反射,但速度更快

本文通过基准测试比较了Java反射、MethodHandles和使用JavaCompiler生成代码的性能。结果显示,反射和MethodHandles的性能约为直接访问的两倍慢,而运行时生成的代码性能与直接访问相当。文章探讨了在Java框架如JPA/Hibernate、XStream中使用这些技术对性能的影响,并建议未来可能优化MethodHandles以提高速度。
摘要由CSDN通过智能技术生成

java 反射 速度

在编译时不知道Java类的最快方法是什么? Java框架通常会这样做。 很多。 它可以直接影响其性能。 因此,让我们对不同的方法进行基准测试,例如反射,方法句柄和代码生成。

用例

假设我们有一个简单的Person类,其中包含名称和地址:

public class Person {
   ...

   public String getName() {...}
   public Address getAddress() {...}

}

并且我们想使用诸如以下的框架:

这些框架都不了解Person类。 因此,他们不能简单地调用person.getName()

// Framework code
   public Object executeGetter(Object object) {
      // Compilation error: class Person is unknown to the framework
      return ((Person) object).getName();
   }

相反,代码使用反射,方法句柄或代码生成。

但是这样的代码被称为很多

  • 如果在数据库中插入1000个不同的人员,则JPA / Hibernate可能会调用2000次这样的代码:
    • 1000次调用Person.getName()
  • 同样,如果您用XML或JSON编写1000个不同的人,则XStream,JAXB或Jackson可能会进行2000次调用。

显然,当这种代码每秒被调用x次时, 其性能很重要

基准测试

使用JMH,我在带有32GB RAM的64位8核Intel i7-4790台式机上的Linux上使用OpenJDK 1.8.0_111运行了一组微型基准测试。 JMH基准测试有3个分支,1秒的5个预热迭代和1秒的20个测量迭代。

基准测试的源代码位于此GitHub存储库中

TL; DR结果

  • Java反射很慢。 (*)
  • Java MethodHandles也很慢。 (*)
  • javax.tools生成的代码很快。 (*)

(*)在用例中,我以使用的工作量作为基准。 你的旅费可能会改变。

因此,魔鬼在细节中。 让我们浏览一下实现,以确认我应用了典型的魔术技巧(例如setAccessible(true) )。

实作

直接访问(基准)

我使用了一个普通的person.getName()调用作为基准:

public final class MyAccessor {

    public Object executeGetter(Object object) {
        return ((Person) object).getName();
    }

}

每次操作大约需要2.7纳秒:

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op

直接访问自然是运行时最快的方法,而没有引导成本。 但是它在编译时导入Person ,因此每个框架都无法使用它。

反射

框架在运行时读取吸气剂而无需事先知道的明显方法是通过Java Reflection:

public final class MyAccessor {

    private final Method getterMethod;

    public MyAccessor() {
        getterMethod = Person.class.getMethod("getName");
        // Skip Java language access checking during executeGetter()
        getterMethod.setAccessible(true);
    }

    public Object executeGetter(Object bean) {
        return getterMethod.invoke(bean);
    }

}

添加setAccessible(true)调用可以使这些反射调用更快,但是即使这样,每个调用也要花费5.5纳秒。

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op
Reflection          avgt   60  5.511 ± 0.081  ns/op

反射比直接访问慢106%(大约慢一倍)。 预热还需要更长的时间。

这对我来说不是什么大惊喜,因为当我使用OptaPlanner在980个城市中描述(使用抽样)一个人为简单的旅行推销员问题时,反射成本像拇指酸痛一样突出:

方法句柄

Java 7中引入了MethodHandle以支持invokedynamic指令。 根据javadoc,它是对基础方法的类型化,直接可执行的引用。 听起来快吧?

public final class MyAccessor {

    private final MethodHandle getterMethodHandle;

    public MyAccessor() {
        MethodHandle temp = lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class));
        temp = temp.asType(temp.type().changeParameterType(0 , Object.class));
        getterMethodHandle = temp.asType(temp.type().changeReturnType(Object.class));
    }

    public Object executeGetter(Object bean) {
        return getterMethodHandle.invokeExact(bean);
    }

}

不幸的是, MethodHandle甚至比 OpenJDK 8中的反射还要慢 。每次操作花费6.1纳秒,因此比直接访问慢132%。

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op
Reflection          avgt   60  5.511 ± 0.081  ns/op
MethodHandle        avgt   60  6.188 ± 0.059  ns/op
StaticMethodHandle  avgt   60  5.481 ± 0.069  ns/op

话虽如此,如果MethodHandle在静态字段中,则每次操作仅需5.5纳秒,这仍然与反射一样慢 。 此外,对于大多数框架而言,这是无法使用的。 例如,JPA实现可能需要反映n类( PersonCompanyOrder等)的m getter( getName()getAddress()getBirthDate() ,...),因此JPA实现如何有n * m静态字段,在编译时不知道nm

我确实希望MethodHandle在将来的Java版本中能够像直接访问一样快,从而取代对...的需求。

使用javax.tools.JavaCompiler生成的代码

在Java中,可以在运行时编译和运行生成的Java代码。 因此,使用javax.tools.JavaCompiler API,我们可以在运行时生成直接访问代码:

public abstract class MyAccessor {

    public static MyAccessor generate() {
        final String String fullClassName = "x.y.generated.MyAccessorPerson$getName";
        final String source = "package x.y.generated;\n"
                + "public final class MyAccessorPerson$getName extends MyAccessor {\n"
                + "    public Object executeGetter(Object bean) {\n"
                + "        return ((Person) object).getName();\n"
                + "    }\n"
                + "}";
        JavaFileObject fileObject = new ...(fullClassName, source);

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        ClassLoader classLoader = ...;
        JavaFileManager javaFileManager = new ...(..., classLoader)
        CompilationTask task = compiler.getTask(..., javaFileManager, ..., singletonList(fileObject));
        boolean success = task.call();
        ...
        Class compiledClass = classLoader.loadClass(fullClassName);
        return compiledClass.newInstance();
    }

    // Implemented by the generated subclass
    public abstract Object executeGetter(Object object);

}

有关如何使用javax.tools.JavaCompiler更多信息,请参见本文本文的 第2页 。 除了javax.tools之外,类似的方法也可以使用ASM或CGLIB,但是这些方法会推断出额外的依赖性,并且可能会产生不同的性能结果。

无论如何, 生成的代码与直接访问一样快

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op
GeneratedCode       avgt   60  2.745 ± 0.025  ns/op

因此,当我再次在OptaPlanner中运行该完全相同的Traveling Salesman问题时,这一次使用代码生成来访问计划变量, 因此总分计算速度提高了18% 。 并且分析(使用采样)看起来也更好:

请注意,在正常使用情况下,由于大量CPU需要实际复杂的分数计算,因此性能提升几乎是无法检测到的...

运行时代码生成的唯一缺点是,它会导致可观的引导成本,特别是如果生成的代码未进行批量编译时。 因此,我仍然希望有一天MethodHandles能够像直接访问一样快,只是为了避免启动成本。

结论

在此基准测试中,反射和MethodHandles的速度是OpenJDK 8中直接访问的两倍,但是生成的代码的速度是直接访问的速度。

Benchmark           Mode  Cnt  Score   Error  Units
===================================================
DirectAccess        avgt   60  2.667 ± 0.028  ns/op
Reflection          avgt   60  5.511 ± 0.081  ns/op
MethodHandle        avgt   60  6.188 ± 0.059  ns/op
StaticMethodHandle  avgt   60  5.481 ± 0.069  ns/op
GeneratedCode       avgt   60  2.745 ± 0.025  ns/op

翻译自: https://www.javacodegeeks.com/2018/01/java-reflection-much-faster.html

java 反射 速度

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值