Javareflection性能
使用reflection创build对象而不是调用类构造函数会导致任何显着的性能差异?
是的,一点没错。 通过反思来查看课程, 其规模更大,成本更高。
引用Java的reflection文档 :
由于reflection涉及dynamicparsing的types,因此无法执行某些Java虚拟机优化。 因此,reflection操作的性能要比非reflection操作的性能要差,应该避免在性能敏感的应用程序中频繁调用的代码部分。
这是一个简单的testing,我在运行Sun JRE 6u10的计算机上在5分钟内破解:
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是所有邪恶的根本
(好吧,可以和#1干一场)
我发誓,如果有人在我工作的时候问过我,我会在接下来的几个月里非常注意他们的代码。
除非您确定需要,否则绝不能进行优化,直到那时,只需编写好的可读代码即可。
哦,我也不是说写愚蠢的代码。 只要想想最干净的方式,你可以做到这一点 – 没有复制和粘贴等(仍然要小心像内循环和使用最适合您的需要的集合 – 忽略这些不是“未优化”编程,这是“坏”编程)
当我听到这样的问题时,它使我感到厌烦,但是我忘记了每个人在真正得到它之前都必须自己学习所有的规则。 在花费了一个月的人为debugging某人“优化”之后,你会得到它。
编辑:
一个有趣的事情发生在这个线程。 检查#1的答案,这是编译器在优化事物方面有多强大的一个例子。 testing是完全无效的,因为非reflection实例化可以被完全分解出来。
课? 直到你写了一个干净,整洁的代码解决scheme,并certificate它太慢之后,才会进行优化。
您可能会发现A a = new A()正由JVM进行优化。 如果你把这些对象放到一个数组中,它们的performance就不那么好。 ;)以下打印…
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纳秒。
在reflection方面有一些开销,但是在现代虚拟机上比以前要小很多。
如果你使用reflection来创build你的程序中的每一个简单的对象,那么就是错误的。 偶尔使用它,当你有充分的理由时,根本不应该是一个问题。
“重要”完全取决于上下文。
如果您使用reflection来创build基于某个configuration文件的单个处理程序对象,然后花费剩余的时间来运行数据库查询,那么这是微不足道的。 如果你通过reflection来创build大量的对象,那么是的,这是很重要的。
一般来说,devise灵活性(在需要的地方)应该推动你使用reflection,而不是性能。 但是,要确定性能是否成问题,您需要进行configuration而不是从论坛获得任意响应。
如果确实需要比reflection快的东西,而且这不仅仅是一个过早的优化,那么使用ASM或更高级别的库生成字节码是一种select。 第一次生成字节码比使用reflection速度要慢,但一旦生成了字节码,它就像普通的Java代码一样快,并且会被JIT编译器优化。
使用代码生成的应用程序的一些示例:
-
CGLIB生成的代理的调用方法比Java的dynamic代理稍快,因为CGLIB为其代理生成字节码,但是dynamic代理只使用reflection( 我测量 CGLIB在方法调用中快了10倍,但是创build代理的速度却比较慢)。
-
JSerial生成字节码来读取/写入序列化对象的字段,而不是使用reflection。 JSerial网站上有一些基准 。
-
我不是100%确定的(现在我不想读源代码),但我认为Guice生成字节码来进行dependency injection。 如我错了请纠正我。
反思是缓慢的,尽pipe对象分配不像反思的其他方面那样绝望。 通过基于reflection的实例化获得等效的性能,需要编写代码,以便jit可以知道正在实例化哪个类。 如果无法确定类别的身份,则分配代码不能内联。 更糟的是,逃逸分析失败,对象不能堆栈分配。 如果你幸运的话,如果这个代码变热了,JVM的运行时分析可能会得到解决,并且可以dynamic地决定哪个类占主导地位,并且可以优化这个类。
要知道这个线程中的微观基准是非常有缺陷的,所以要带上一粒盐。 彼得·劳瑞(Peter Lawrey)所说的最less的缺陷就是:它是通过热身运行来获得方法的,而且它(有意识地)击败逃逸分析来确保分配实际上正在发生。 但即使这样也有问题,例如,数量巨大的arrays商店可能会打败caching和存储缓冲区,所以如果您的分配速度非常快,这将成为内存基准。 (对彼得而言,得出的结论是正确的:差距是“150ns”而不是“2.5x”,我怀疑他是以这种方式谋生的)
是的,使用Reflection时性能会受到影响,但是对于优化可能的解决方法是caching方法:
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]调用方法100万次查找了5618毫秒
[java]调用方法1000000次reflection与caching了270毫秒
是的,这是显着慢。 我们正在运行一些代码,虽然目前还没有可用的指标,但最终的结果是,我们不得不重构该代码,以避免使用reflection。 如果你知道类是什么,只需直接调用构造函数。
有趣的是,设置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
在doReflection()是由于Class.forName(“misc.A”)(这将需要一个类的查找,可能扫描filsystem上的类path),而不是在类上调用newInstance()的开销。 我想知道如果Class.forName(“misc.A”)只在for循环之外完成一次,那么stats看起来会是什么样子,对于每个循环的调用,都不需要做。
是的,因为JVM不能在编译时间上优化代码,所以总是会慢一点来reflection创build一个对象。 有关更多详细信息,请参阅Sun / Java Reflection教程 。
看到这个简单的testing:
public class TestSpeed { public static void main(String[] args) { long startTime = System.nanoTime(); Object instance = new TestSpeed(); long endTime = System.nanoTime(); System.out.println(endTime - startTime + "ns"); startTime = System.nanoTime(); try { Object reflectionInstance = Class.forName("TestSpeed").newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } endTime = System.nanoTime(); System.out.println(endTime - startTime + "ns"); } }
通常你可以使用Apache Commons BeanUtils或者PropertyUtils,它们可以自省(基本上它们caching关于类的元数据,所以它们并不总是需要使用reflection)。
我认为这取决于目标方法的轻重。 如果目标方法很轻(如getter / setter),则可能会慢1〜3倍。 如果目标方法需要大约1毫秒或更多,那么性能将非常接近。 这里是我用Java 8做的testing,并反思 :
public class ReflectionTest extends TestCase { @Test public void test_perf() { Profiler.run(3, 100000, 3, "m_01 by refelct", () -> Reflection.on(X.class)._new().invoke("m_01")).printResult(); Profiler.run(3, 100000, 3, "m_01 direct call", () -> new X().m_01()).printResult(); Profiler.run(3, 100000, 3, "m_02 by refelct", () -> Reflection.on(X.class)._new().invoke("m_02")).printResult(); Profiler.run(3, 100000, 3, "m_02 direct call", () -> new X().m_02()).printResult(); Profiler.run(3, 100000, 3, "m_11 by refelct", () -> Reflection.on(X.class)._new().invoke("m_11")).printResult(); Profiler.run(3, 100000, 3, "m_11 direct call", () -> X.m_11()).printResult(); Profiler.run(3, 100000, 3, "m_12 by refelct", () -> Reflection.on(X.class)._new().invoke("m_12")).printResult(); Profiler.run(3, 100000, 3, "m_12 direct call", () -> X.m_12()).printResult(); } public static class X { public long m_01() { return m_11(); } public long m_02() { return m_12(); } public static long m_11() { long sum = IntStream.range(0, 10).sum(); assertEquals(45, sum); return sum; } public static long m_12() { long sum = IntStream.range(0, 10000).sum(); assertEquals(49995000, sum); return sum; } } }
完整的testing代码在GitHub: ReflectionTest.java上可用