使用反射创建对象而不是调用类构造函数会导致任何显着的性能差异吗?
是的,一点没错.通过反思查找课程的幅度更大.
引用Java关于反射的文档:
由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化.因此,反射操作的性能低于非反射操作,并且应避免在性能敏感应用程序中频繁调用的代码段中.
这是一个简单的测试,我在机器上运行5分钟,运行Sun JRE 6u10:
public class Main { public static void main(String[] args) throws Exception { doRegular(); doReflection(); } public static void doRegular() throws Exception { long start = System.currentTimeMillis(); for (int i=0; i<1000000; i++) { A a = new A(); a.doSomeThing(); } System.out.println(System.currentTimeMillis() - start); } public static void doReflection() throws Exception { long start = System.currentTimeMillis(); for (int i=0; i<1000000; i++) { A a = (A) Class.forName("misc.A").newInstance(); a.doSomeThing(); } System.out.println(System.currentTimeMillis() - start); } }
有了这些结果:
35 // no reflection 465 // using reflection
请记住,查找和实例化是一起完成的,在某些情况下,查找可以重构,但这只是一个基本的例子.
即使您只是实例化,您仍然会受到性能影响:
30 // no reflection 47 // reflection using one lookup, only instantiating
再次,YMMV.
是的,它慢了.
但请记住该死的#1规则 - PREMATURE OPTIMIZATION是所有邪恶的根源
(好吧,DRY可能与#1并列)
我发誓,如果有人在工作时向我求助并告诉我这一点,我会对他们未来几个月的代码保持警惕.
在您确定需要它之前,您必须永远不会优化,直到那时,只需编写好的,可读的代码.
哦,我也不是说写愚蠢的代码.只是考虑一下你可以做到的最干净的方式 - 没有复制和粘贴等等(仍然要警惕内部循环和使用最符合你需要的集合 - 忽略这些不是"未经优化的"编程,这是"糟糕"的编程)
当我听到这样的问题时,它吓坏了我,但后来我忘了每个人都必须在他们真正得到它之前先学习所有的规则.你花了一个月的时间调试一些"优化"的东西后你会得到它.
编辑:
这个帖子发生了一件有趣的事情.检查#1答案,这是编译器在优化方面有多强大的一个例子.测试完全无效,因为可以完全排除非反射实例化.
课?在你编写一个干净,整齐编码的解决方案并证明它太慢之前,不要进行优化.
您可能会发现J a正在优化A a = new A().如果将对象放入数组中,它们的表现就不会那么好.;)以下打印...
new A(), 141 ns A.class.newInstance(), 266 ns new A(), 103 ns A.class.newInstance(), 261 ns public class Run { private static final int RUNS = 3000000; public static class A { } public static void main(String[] args) throws Exception { doRegular(); doReflection(); doRegular(); doReflection(); } public static void doRegular() throws Exception { A[] as = new A[RUNS]; long start = System.nanoTime(); for (int i = 0; i < RUNS; i++) { as[i] = new A(); } System.out.printf("new A(), %,d ns%n", (System.nanoTime() - start)/RUNS); } public static void doReflection() throws Exception { A[] as = new A[RUNS]; long start = System.nanoTime(); for (int i = 0; i < RUNS; i++) { as[i] = A.class.newInstance(); } System.out.printf("A.class.newInstance(), %,d ns%n", (System.nanoTime() - start)/RUNS); } }
这表明我的机器差异大约为150 ns.
"重要"完全取决于背景.
如果您使用反射基于某个配置文件创建单个处理程序对象,然后花费其余的时间来运行数据库查询,那么它是无关紧要的.如果你通过紧密循环中的反射创建大量对象,那么是的,它是重要的.
通常,设计灵活性(在需要的地方!)应该驱动您使用反射,而不是性能.但是,要确定性能是否是一个问题,您需要分析而不是从讨论论坛获取任意响应.
如果确实需要比反射更快的东西,并且它不仅仅是一个过早的优化,那么使用ASM或更高级别的库生成字节码是一种选择.第一次生成字节码比仅使用反射慢,但是一旦生成字节码,它就像普通的Java代码一样快,并且将由JIT编译器优化.
使用代码生成的应用程序的一些示例:
在CGLIB生成的代理上调用方法比Java的动态代理略快,因为CGLIB为其代理生成字节码,但动态代理仅使用反射(我测量 CGLIB在方法调用中快10倍左右,但创建代理的速度较慢).
JSerial生成字节码,用于读取/写入序列化对象的字段,而不是使用反射.JSerial网站上有一些基准测试.
我不是100%肯定(我现在不想阅读源代码),但我认为Guice会生成字节码来执行依赖注入.如我错了请纠正我.
反射有一些开销,但它在现代VM上比以前要小很多.
如果你使用反射来创建程序中的每个简单对象,那么就会出现问题.偶尔使用它,当你有充分的理由时,根本不应该成为一个问题.
是的,使用Reflection时会出现性能损失,但优化的可能解决方法是缓存该方法:
Method md = null; // Call while looking up the method at each iteration. millis = System.currentTimeMillis( ); for (idx = 0; idx < CALL_AMOUNT; idx++) { md = ri.getClass( ).getMethod("getValue", null); md.invoke(ri, null); } System.out.println("Calling method " + CALL_AMOUNT+ " times reflexively with lookup took " + (System.currentTimeMillis( ) - millis) + " millis"); // Call using a cache of the method. md = ri.getClass( ).getMethod("getValue", null); millis = System.currentTimeMillis( ); for (idx = 0; idx < CALL_AMOUNT; idx++) { md.invoke(ri, null); } System.out.println("Calling method " + CALL_AMOUNT + " times reflexively with cache took " + (System.currentTimeMillis( ) - millis) + " millis");
将导致:
[java]用查找反射性地调用方法1000000次,耗时5618毫秒
[java]用缓存反复调用方法1000000次需要270毫安
反射很慢,尽管对象分配并不像反射的其他方面那样没有希望.通过基于反射的实例化实现相同的性能需要您编写代码,以便jit可以告诉实例化哪个类.如果无法确定类的标识,则无法内联分配代码.更糟糕的是,转义分析失败,并且对象无法进行堆栈分配.如果幸运的话,如果此代码变热,JVM的运行时分析可能会受到拯救,并且可以动态地确定哪个类占优势并且可以针对该类进行优化.
请注意,此线程中的微基准测试存在严重缺陷,因此请使用一粒盐.迄今为止最缺陷的是彼得·劳里(Peter Lawrey):它确实进行了热身运动以使方法进行了操作,并且它(有意识地)击败逃逸分析以确保分配实际发生.即使那个问题也存在问题:例如,大量的数组存储可能会导致缓存缓存和存储缓冲区,因此如果分配速度非常快,这将最终成为内存基准测试.(虽然得到了正确的结论,但彼得得到了结论:不同之处是"150ns"而不是"2.5x".我怀疑他是为了生活而做这件事.)
有趣的是,设置了跳过安全检查的setAccessible(true),成本降低了20%.
没有setAccessible(true)
new A(), 70 ns A.class.newInstance(), 214 ns new A(), 84 ns A.class.newInstance(), 229 ns
使用setAccessible(true)
new A(), 69 ns A.class.newInstance(), 159 ns new A(), 85 ns A.class.newInstance(), 171 ns
是的,它明显变慢了.我们正在运行一些代码,虽然我目前没有可用的指标,但最终结果是我们不得不重构该代码而不使用反射.如果你知道类是什么,只需直接调用构造函数.