我一直回避基于其缓慢的声誉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");
}
}
因此,其实,我的问题是
您的测试可能存在缺陷。 一般来说尽管JVM可以优化正常的实例,但不能作出反射使用情况的优化 。
对于那些想知道什么时间是,我添加了一个预热阶段和使用的阵列保持创建的对象(更类似于一个真正的程序可能会做)。 我跑我的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");
}
}
反思是几个原因很明显慢:
- 编译器可以做到没有任何的优化,因为它可以对你正在做什么,没有真正的想法。 这大概无二
JIT
以及 - 一切都被调用/创造了被发现 (即类抬头的名字,方法看着火柴等)
- 参数需要通过装箱/拆箱到打扮,包装成数组,
Exceptions
裹着InvocationTargetException
S和重新抛出等等。 - 所有的处理乔恩斯基特提到这里 。
仅仅因为一些是100X慢并不意味着它是给你的假设反思是“正道”为您设计您的程序太慢 。 例如,我想象的IDE大量使用反射和我的IDE主要是从性能的角度确定。
毕竟, 反射的开销很可能是小巫见大巫 时 ,比如说相比 ,解析XML或访问数据库 !
另外要记住的一点是, 微基准测试是确定的东西快是如何在实践中出了名的有缺陷的机制 。 除了蒂姆·本德的言论时,JVM需要时间“热身”时,JIT可以重新优化代码热点上即时等。
为实例B中的实时编译的代码是令人难以置信的轻巧。 基本上,它需要分配足够的内存(这是刚刚递增,除非需要GC指针),这就是它 - 没有构造函数的代码来真的叫; 我不知道是否JIT跳过它或没有,但无论哪种方式,有没有很多工作要做。
相比之下,与反射必须做的一切:
- 检查是否有一个参数的构造函数
- 检查参数的构造函数的可访问性
- 检查调用者可以访问使用反射在所有
- 工作了(在执行时)需要多少空间来分配
- 打电话到构造函数代码(因为它不会预先知道,构造函数为空)
...大概其他的事情我还没有想到的。
通常反射不是在性能关键上下文中使用; 如果你需要这样的动态行为,你可以使用类似BCEL代替。
看来,如果你做的构造函数访问,它会执行得更快。 现在,它比其他版本慢了约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
如果你使用服务器VM,它能够更加优化,所以,这只是3-4倍慢。 这是很典型的表现。 文章认为地理链接是一个很好看的。
Normal instaniation took: 47ms
Reflecting instantiation took:140ms
但是,如果你能标替换-XX:+ DoEscapeAnalysis那么JVM能够优化常规实例远(这将是0-15ms),而反射实例保持不变。
@Tim本德尔的代码给我的机器上,这些结果(jdk_1.8_45,OS_X 10.10,i7处理器,16G):
Reflecting instantiation took:1139ms Normal instaniation took: 4969ms
如此看来现代JVM,反射代码也将被优化的不错。