我总是根据其缓慢的声誉来避免Java反射。我在当前项目的设计中达到了一个重点,能够使用它会使我的代码更具可读性和优雅性,所以我决定试一试。
我对这种差异感到非常惊讶,有时我注意到运行时间延长了近100倍。即使在这个简单的例子中,它只是实例化一个空类,它也令人难以置信。
class B {
}
public class Test {
public static long timeDiff(long old) {
return System.currentTimeMillis() - old;
}
public static void main(String args[]) throws Exception {
long numTrials = (long) Math.pow(10, 7);
long millis;
millis = System.currentTimeMillis();
for (int i=0; i<numTrials; i++) {
new B();
}
System.out.println("Normal instaniation took: "
+ timeDiff(millis) + "ms");
millis = System.currentTimeMillis();
Class<B> c = B.class;
for (int i=0; i<numTrials; i++) {
c.newInstance();
}
System.out.println("Reflecting instantiation took:"
+ timeDiff(millis) + "ms");
}
}
所以,我的问题是
为什么这么慢?有什么我做错了吗? (甚至上面的例子也证明了这一点)。我很难相信它实际上比正常实例慢100倍。
还有什么其他东西可以更好地用于将代码视为数据(请记住,我现在仍然坚持使用Java)
答案 0 :(得分:42)
由于一些显而易见的原因,反思很慢:
JIT
Exceptions
包裹在InvocationTargetException
中并重新抛出等。只是因为某些东西慢了100倍并不意味着它太慢了假设反射是你设计程序的“正确方法”。例如,我认为IDE会大量使用反射,从性能角度来看,我的IDE基本上是可以的。
毕竟,当与相比时,反射的开销可能苍白无足轻重,比如解析XML或访问数据库!
要记住的另一点是,微基准是一个众所周知的有缺陷的机制,用于确定某些事物在实践中的速度。与Tim Bender's remarks一样,JVM需要时间来“预热”,JIT可以在运行中重新优化代码热点等。
答案 1 :(得分:39)
对于那些想知道时代的人,我添加了一个预热阶段并使用数组来维护创建的对象(更类似于真正的程序可能做的事情)。我在我的OSX,jdk7系统上运行了测试代码并得到了这个:
反映实例化:5180ms
正常的实例需要:2001ms
修改测试:
public class Test {
static class B {
}
public static long timeDiff(long old) {
return System.nanoTime() - old;
}
public static void main(String args[]) throws Exception {
int numTrials = 10000000;
B[] bees = new B[numTrials];
Class<B> c = B.class;
for (int i = 0; i < numTrials; i++) {
bees[i] = c.newInstance();
}
for (int i = 0; i < numTrials; i++) {
bees[i] = new B();
}
long nanos;
nanos = System.nanoTime();
for (int i = 0; i < numTrials; i++) {
bees[i] = c.newInstance();
}
System.out.println("Reflecting instantiation took:" + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms");
nanos = System.nanoTime();
for (int i = 0; i < numTrials; i++) {
bees[i] = new B();
}
System.out.println("Normal instaniation took: " + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms");
}
}
答案 2 :(得分:27)
用于实例化B的JITted代码令人难以置信的轻量级。基本上它需要分配足够的内存(除非需要GC,否则只是递增指针)而且就是这样 - 没有构造函数代码可以真正调用;我不知道JIT是否会跳过它,但无论如何都没有太多可做的事情。
将其与反射必须做的所有事情进行比较:
......可能还有其他一些我甚至都没想过的事情。
通常,在性能关键的上下文中不使用反射;如果你需要这样的动态行为,你可以改用BCEL之类的东西。
答案 3 :(得分:10)
似乎如果你使构造函数可访问,它将执行得更快。现在它只比其他版本慢大约10-20倍。
Constructor<B> c = B.class.getDeclaredConstructor();
c.setAccessible(true);
for (int i = 0; i < numTrials; i++) {
c.newInstance();
}
Normal instaniation took: 47ms
Reflecting instantiation took:718ms
如果您使用服务器虚拟机,它可以对其进行更多优化,因此速度只会慢3-4倍。这是非常典型的表现。 Geo链接的The article是一个很好的阅读。
Normal instaniation took: 47ms
Reflecting instantiation took:140ms
但是如果你使用-XX:+ DoEscapeAnalysis启用标量替换,那么JVM能够优化常规实例化(它将是0-15ms),但反射实例化保持不变。
答案 4 :(得分:3)
答案 5 :(得分:0)
@Tim Bender的代码在我的机器上提供这些结果(jdk_1.8_45,os_x 10.10,i7,16G):
Reflecting instantiation took:1139ms
Normal instaniation took: 4969ms
所以在现代JVM中,反射代码也会很好地优化。