我一直基于Java的缓慢性而避免使用Java反射soley。 我在当前项目的设计中达到了可以使用它的目的,这将使我的代码更具可读性和雅致性,因此我决定尝试一下。
两者之间的差异让我感到惊讶,有时我会发现运行时间几乎快了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
new B();
}
System.out.println("Normal instaniation took: "
+ timeDiff(millis) + "ms");
millis = System.currentTimeMillis();
Class c = B.class;
for (int i=0; i
c.newInstance();
}
System.out.println("Reflecting instantiation took:"
+ timeDiff(millis) + "ms");
}
}
真的,我的问题是
为什么这么慢? 我做错什么了吗? (即使上面的示例也说明了区别)。 我很难相信它真的比普通实例慢100倍。
还有别的可以更好地用于将代码视为数据的东西吗(请记住,我暂时仍然使用Java)
6个解决方案
42 votes
反映缓慢的原因有几个:
编译器无法进行任何优化,因为它无法真正了解您的工作。 这可能也适用于Exceptions
必须发现正在调用/创建的所有内容(即按名称查找类,查找匹配项的方法等)
参数需要通过装箱/拆箱,包装成阵列,包装在InvocationTargetExceptions中的Exceptions并重新抛出等方式进行修饰。
Jon Skeet在这里提到的所有处理。
仅仅因为某些东西慢了100倍并不意味着它就太慢了,因为您认为反射是设计程序的“正确方法”。 例如,我认为IDE大量使用了反射,从性能的角度来看,我的IDE基本上还可以。
毕竟,与解析XML或访问数据库相比,反射的开销可能显得微不足道!
要记住的另一点是,微基准测试在确定某些事物在实践中的速度方面是一个臭名昭著的机制。 除了Tim Bender的话,JVM还需要一些时间来“热身”,JIT可以即时地重新优化代码热点等。
oxbow_lakes answered 2020-06-25T17:04:45Z
40 votes
您的测试可能有缺陷。 通常,尽管JVM可以优化常规实例,但不能针对反射用例进行优化。
对于那些想知道现在几点的人,我添加了一个预热阶段,并使用数组来维护创建的对象(与实际程序可能执行的操作更相似)。 我在OSX,jdk7系统上运行了测试代码,并得到了以下信息:
反映实例耗时:5180ms
正常执行时间:2001毫秒
修改后的测试:
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 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");
}
}
Tim Bender answered 2020-06-25T17:03:52Z
27 votes
用于实例化B的JITted代码非常轻巧。 基本上,它需要分配足够的内存(除非需要GC,否则仅增加一个指针)就可以了-确实没有构造函数代码可以调用。 我不知道JIT是否跳过它,但是无论如何都没有很多事情要做。
将其与反射所要做的一切进行比较:
检查是否有一个无参数的构造函数
检查无参数构造函数的可访问性
检查呼叫者是否有权使用反射
算出(在执行时)需要分配多少空间
调用构造函数代码(因为它事先不会知道构造函数为空)
...可能还有其他我从未想过的事情。
通常,在性能关键的上下文中不使用反射。 如果您需要这样的动态行为,则可以使用BCEL之类的方法。
Jon Skeet answered 2020-06-25T17:05:41Z
10 votes
看来,如果使构造函数可访问,它将更快地执行。 现在,它仅比其他版本慢10到20倍。
Constructor 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倍。 这是非常典型的性能。 与地理链接相关的文章是一本好书。
Normal instaniation took: 47ms
Reflecting instantiation took:140ms
但是,如果使用-XX:+ DoEscapeAnalysis启用标量替换,那么JVM可以优化常规实例化(它将是0-15ms),但是反射实例化保持不变。
Esko Luontola answered 2020-06-25T17:06:10Z
3 votes
首次引入反射时速度很慢,但是在更新的JRE中已大大加快了反射速度
不过,在内部循环中使用反射可能不是一个好主意
基于反射的代码对于基于JIT的优化的潜力很小
反射主要用于连接松耦合的组件,即用于查找仅已知接口的具体类和方法:依赖注入框架,实例化JDBC实现类或XML解析器。 这些使用通常可以在系统启动时完成一次,因此,效率低下并不重要!
mfx answered 2020-06-25T17:06:44Z
0 votes
@Tim Bender的代码在我的机器上给出以下结果(jdk_1.8_45,os_x 10.10,i7、16G):
Reflecting instantiation took:1139ms
Normal instaniation took: 4969ms
因此,在现代JVM上,反射代码也将得到优化。
bob answered 2020-06-25T17:07:13Z