我想知道是否可以实现任何其他优化来提高Java中反射调用的速度。 并不是说性能会令人望而却步,而是当我想到我正在编写的某个库中的某些代码在某个地方的紧密循环中实现时,我会大吃一惊。
考虑一种实用方法来进行反射性调用:
public static Object invoke(Object targetObject, String methodName, Object[] arguments, Class< ? >[] signature)
基本操作是
return method.invoke(targetObject, arguments);
作为性能优化,我使用目标对象的类,方法名称和签名(其代码可能需要一些改进)的哈希值来缓存该方法,但是除此之外,我还能做点什么? 我听说过一些有关InvokeDynamic早期实现的参考,这听起来很有希望,但我只是假设它们可能尚不适用,因此我放弃了自己的字节码操作,因为我想使实用程序保持简单(但快速)。
干杯。
以下注释与Sun的实现有关,特别是与OpenJDK 6有关。您的工作量可能会因其他Java平台实现而异。
java.lang.Class本身会进行一些缓存,因此实??现自己的缓存可能不会带来很多改善。使用和不使用手动缓存进行计时测试。
实际的调用机制也已优化。使用JNI调用反射方法的前15次运行(默认情况下);之后,将生成字节码,并且调用该反射方法的效果与直接在Java代码中调用该方法的效果相同。
哇,我从来不知道。我将进行这些计时测试。有什么办法可以覆盖15个默认值?还有其他办法说服编译器尽早启动吗?
只需将sun.reflect.inflationThreshold属性设置为您喜欢的数字即可。
我围绕Chris Jester-Young的答案进行了一些测试,并使用了详细选项,我确实观察到编译器在第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
t.jar]
[Loaded sun.reflect.AccessorGenerator from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.MethodAccessorGenerator from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.ByteVectorFactory from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.ByteVector from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.ByteVectorImpl from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.ClassFileAssembler from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.UTF8 from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded java.lang.Void from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.Label from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.Label$PatchInfo from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded java.util.AbstractList$Itr from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.MethodAccessorGenerator$1 from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.ClassDefiner from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.ClassDefiner$1 from C:\jdk1.5.0_06\jre\lib
t.jar]
[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
Test# 15
Test# 16
Test# 17
我猜想InvokeDynamic业务在反射加速/消除的基础上不会吸引太多开发人员。
谢谢克里斯。
您肯定只想反射一次方法对象(可能作为私有静态对象),并将其用于调用而不是每次都将其反映出来。除非您在编译时不知道名称,否则不要理会缓存映射。
如果在您的上下文中是明智的,则您可能希望继续使用并重用参数数组对象(不要忘记在方法退出时将数组元素设置为空,以防止临时禁止GC)。
如果始终使用相同的parm调用(极不可能),则可以挂在parms上,并且(带有其值的参数数组)可以重用它们。
由于其他答案已经给出的原因,我不会尝试任何其他尝试。
如果调用的成本不到该方法执行成本的10%,则几乎不必担心。
您可以通过在有无例程的情况下循环运行10 ^ 6次来确定它。用秒表计时,因此秒转换为微秒。
百分比成本将完全取决于该方法的实际作用。
@Renesis:对。那就是我的意思。与在方法内部进行有用的工作相比,进入和退出方法所花费的时间可能很小。如果是这样的话,优化进出就只会有所帮助。
我的观点是,如果方法本身非常轻巧(一次分配或仅返回一个值),则方法调用本身的类型可能会产生很大的影响(汇总到许多调用中)。
@Renesis:那我想是在同一页上。
过早优化通常是不好的。无论如何,您的性能仍将是动态语言的许多倍,因此,除了记录它使用反射并因此可能不是最佳的事实之外,我真的不会担心它。
而且,现在或将来,Java都有很大可能会优化字节码,这样,在循环中使用时,调用不会比方法调用花费更多。您的"优化"实际上可能会阻碍编译器执行类似操作的能力。 (我知道我很模糊,但这已经发生了-很多)。
承认,但是在偷偷摸摸的过程中,我发现非公共方法需要爬上继承树以在父级中找到方法,因此我发现某些情况下需要多次调用get [Declared] Method。另外,我只需要调用一次setAccessible(true)。我还在基地吗?
我不认为这是一个有趣的分析,但是除非您发现必要,否则您应该始终使用您能想到的最易读,最简单和直接的解决方案。