【问题标题】:Any way to further optimize Java reflective method invocation?有什么方法可以进一步优化 Java 反射方法调用?
【发布时间】:2010-09-29 17:04:53
【问题描述】:

我想知道是否可以实施任何其他优化来提高 Java 中反射调用的速度。并不是说性能令人望而却步,但是当我想到我正在编写的库中的某些代码在某个地方以紧密的循环实现时,我感到很沮丧。

考虑一个实用方法来反射调用:

public static Object invoke(Object targetObject, String methodName, Object[] arguments, Class<?>[] signature)

基本操作是

return method.invoke(targetObject, arguments);

作为性能优化,我使用目标对象的类、方法名称和签名的哈希缓存方法(其代码可能会有所改进),但除此之外,我还能做些什么吗?我听说过对 InvokeDynamic 的一些早期实现的引用,这听起来很有希望,但我只是假设它们可能还不适用,并且我不考虑我自己的字节码操作,因为我想保持实用程序简单(但很快)。

干杯。

【问题讨论】:

    标签: java performance reflection


    【解决方案1】:

    下面的 cmets 与 Sun 的实现有关,特别是 OpenJDK 6。您的里程可能会因其他 Java 平台实现而异。

    java.lang.Class 自己做一些缓存,所以实现你自己的缓存可能不会改善很多事情。在有和没有手动缓存的情况下进行计时测试。

    实际的调用机制也进行了优化。反射方法的前 15 次运行(默认情况下)是使用 JNI 调用的;之后,生成字节码,调用该反射方法的执行与直接在 Java 代码中调用该方法相同。

    【讨论】:

    • 哇,我从来不知道。我会做那些时间测试。有没有办法覆盖 15 默认值?有没有其他方法可以说服编译器尽早启动?
    • 只需将 sun.reflect.inflationThreshold 属性设置为您喜欢的数字即可。
    【解决方案2】:

    过早的优化通常是不好的。无论如何,你的性能仍然是动态语言的许多倍,所以除了记录它使用反射因此可能不是最佳的事实之外,我真的不会担心它。

    此外,现在或将来,Java 很有可能会优化字节码,以便在循环中使用时,调用的成本不会超过方法调用。您的“优化”实际上可能会阻碍编译器执行此类操作的能力。 (我知道我含糊其辞,但这已经发生了——很多)。

    【讨论】:

    • 承认了,但是在偷偷摸摸的过程中,我发现非公共方法需要爬继承树才能找到父类中的方法,所以我发现有些情况需要调用get[Declared]方法几个次。另外,我只需要调用一次 setAccessible(true)。我还在基地吗?
    • 我不认为你离题了,这是一个有趣的分析,但除非你发现有必要,否则你应该总是使用你能想到的最易读、最简单和最直接的解决方案.
    【解决方案3】:

    您肯定希望只反射一次方法对象(可能作为私有静态),并将其用于调用而不是每次都将其反射出来。除非您在编译时不知道名称,否则不要打扰缓存映射。

    如果它在您的上下文中是明智的,您可能希望保留并重用参数数组对象(不要忘记在方法退出时将数组元素归空以防止临时 GC 抑制) .

    如果总是使用相同的参数调用(极不可能),您可以挂在参数上并(带有它的值的参数数组)重用它们。

    由于其他答案已经给出的原因,我不会尝试任何其他事情。

    【讨论】:

      【解决方案4】:

      我围绕 Chris Jester-Young 的回答运行了一些测试,并使用 verbose 选项,我确实观察到编译器在第 15 次调用前后采取了一些措施。如果没有更复杂的测试,很难说是否存在很大的性能差异,但它是有说服力的。这是输出:

      Test# 0
      Test# 1
      Test# 2
      Test# 3
      Test# 4
      Test# 5
      Test# 6
      Test# 7
      Test# 8
      Test# 9
      Test# 10
      Test# 11
      Test# 12
      Test# 13
      Test# 14
      [Loaded sun.reflect.ClassFileConstants from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.AccessorGenerator from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.MethodAccessorGenerator from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.ByteVectorFactory from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.ByteVector from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.ByteVectorImpl from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.ClassFileAssembler from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.UTF8 from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded java.lang.Void from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.Label from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.Label$PatchInfo from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded java.util.AbstractList$Itr from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.MethodAccessorGenerator$1 from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.ClassDefiner from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.ClassDefiner$1 from C:\jdk1.5.0_06\jre\lib\rt.jar]
      [Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
      Test# 15
      Test# 16
      Test# 17
      

      我猜 InvokeDynamic 业务在反射加速/消除的基础上并没有吸引太多的开发人员。

      谢谢克里斯。

      【讨论】:

        【解决方案5】:

        如果调用成本低于方法中发生的成本的 10%,则几乎不值得担心。

        您可以通过在循环中运行 10^6 次来确定这一点,无论有无例程的内容。用秒表计时,因此秒转换为微秒。

        【讨论】:

        • 百分比成本完全取决于该方法实际在做什么。
        • @Reesis:对。这就是我的观点。与在方法内进行有用工作所花费的时间相比,进入和退出该方法所花费的时间可能很小。如果是这样,优化进入和退出只能在百分比方面有所帮助。
        • 我的观点是,如果方法本身非常轻量(单个赋值或仅返回一个值),则方法调用本身的类型可能会产生很大影响(聚合到许多调用中)。
        • @Reensis:那么我认为我们在同一页上。
        猜你喜欢
        • 2011-10-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-27
        相关资源
        最近更新 更多