Java 反射:为什么这么慢?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1392351/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java Reflection: Why is it so slow?
提问by Mike
I've always avoided Java reflection soley based on its reputation for slowness. I reached a point in the design of my current project where being able to use it would make my code much more readable and elegant, so I decided to give it a go.
我一直避免使用 Java 反射,因为它以缓慢而著称。我在当前项目的设计中达到了一个点,能够使用它会使我的代码更具可读性和优雅性,所以我决定试一试。
I was simply astonished by the difference, I noticed at times almost 100x longer run times. Even in this simple example where it just instantiates an empty class, it's unbelievable.
我只是对这种差异感到惊讶,我注意到有时运行时间几乎长了 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");
}
}
So really, my questions are
真的,我的问题是
Why is it this slow? Is there something I'm doing wrong? (even the example above demonstrates the difference). I have a hard time believing that it can really be 100x slower than normal instantiation.
Is there something else that can be better used for treating code as Data (bear in mind I'm stuck with Java for now)
为什么这么慢?有什么我做错了吗?(即使是上面的例子也证明了差异)。我很难相信它真的可以比正常实例化慢 100 倍。
是否有其他东西可以更好地用于将代码视为数据(请记住,我现在坚持使用 Java)
采纳答案by Tim Bender
Your test may be flawed. Generally though the JVM may optimize the normal instantiation but could not make optimizations for the reflective use case.
你的测试可能有缺陷。通常虽然JVM可以优化正常的实例化,但不能对反射用例进行优化。
For those wondering what the times were, I added a warmup phase and used an array to maintain the created objects (more similar to what a real program might do). I ran the test code on my OSX, jdk7 system and got this:
对于那些想知道时间是什么的人,我添加了一个预热阶段并使用一个数组来维护创建的对象(更类似于真实程序可能会做的事情)。我在我的 OSX、jdk7 系统上运行了测试代码并得到了这个:
Reflecting instantiation took:5180ms
Normal instaniation took: 2001ms
反射实例化耗时:5180ms
正常实例化耗时:2001ms
Modified test:
修改测试:
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");
}
}
回答by oxbow_lakes
Reflection is slow for a few obvious reasons:
反射很慢有几个明显的原因:
- The compiler can do no optimization whatsoever as it can have no real idea about what you are doing. This probably goes for the
JIT
as well - Everything being invoked/created has to be discovered(i.e. classes looked up by name, methods looked at for matches etc)
- Arguments need to be dressed up via boxing/unboxing, packing into arrays,
Exceptions
wrapped inInvocationTargetException
s and re-thrown etc. - All the processing that Jon Skeetmentions here.
- 编译器无法进行任何优化,因为它无法真正了解您在做什么。这大概无二
JIT
以及 - 必须发现所有被调用/创建的东西(即按名称查找的类,查找匹配的方法等)
- 参数需要通过装箱/拆箱、打包成数组、
Exceptions
包裹在InvocationTargetException
s 中并重新抛出等来修饰。 - Jon Skeet在这里提到的所有处理。
Just because something is 100x slower does not mean it is too slow for youassuming that reflection is the "right way" for you to design your program. For example, I imagine that IDEs make heavy use of reflection and my IDE is mostly OK from a performance perspective.
仅仅因为某些东西慢 100 倍并不意味着它对你来说太慢了,假设反射是你设计程序的“正确方法”。例如,我认为 IDE 会大量使用反射,而我的 IDE 从性能角度来看基本没问题。
After all, the overhead of reflectionis likely to pale into insignificancewhen compared with, say, parsing XML or accessing a database!
毕竟,反射的开销很可能是小巫见大巫时相比,比如说,解析XML或访问数据库!
Another point to remember is that micro-benchmarks are a notoriously flawed mechanism for determining how fast something is in practice. As well as Tim Bender'sremarks, the JVM takes time to "warm up", the JIT can re-optimize code hotspots on-the-fly etc.
要记住的另一点是,微基准是一种众所周知的有缺陷的机制,用于确定某事在实践中的速度。以及Tim Bender 的评论,JVM 需要时间来“预热”,JIT 可以即时重新优化代码热点等。
回答by Jon Skeet
The JITted code for instantiating B is incrediblylightweight. Basically it needs to allocate enough memory (which is just incrementing a pointer unless a GC is required) and that's about it - there's no constructor code to call really; I don't know whether the JIT skips it or not but either way there's not a lot to do.
用于实例化 B 的 JITted 代码非常轻量级。基本上它需要分配足够的内存(这只是增加一个指针,除非需要 GC),仅此而已 - 没有真正调用的构造函数代码;我不知道 JIT 是否跳过它,但无论哪种方式都没有太多事情要做。
Compare that with everything that reflection has to do:
将其与反射必须做的一切进行比较:
- Check that there's a parameterless constructor
- Check the accessibility of the parameterless constructor
- Check that the caller has access to use reflection at all
- Work out (at execution time) how much space needs to be allocated
- Call into the constructor code (because it won't know beforehand that the constructor is empty)
- 检查是否存在无参数构造函数
- 检查无参数构造函数的可访问性
- 检查调用者是否有权使用反射
- 计算(在执行时)需要分配多少空间
- 调用构造函数代码(因为它不会事先知道构造函数为空)
... and probably other things I haven't even thought of.
......可能还有其他我什至没有想到的事情。
Typically reflection isn't used in a performance-critical context; if you need dynamic behaviour like that, you could use something like BCELinstead.
通常在性能关键的上下文中不使用反射;如果你需要这样的动态行为,你可以使用像BCEL这样的东西。
回答by Esko Luontola
It seems that if you make the constructor accessible, it will execute much faster. Now it's only about 10-20 times slower than the other version.
似乎如果您使构造函数可访问,它将执行得更快。现在它只比其他版本慢 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
And if you use the Server VM, it is able to optimize it more, so that it's only 3-4 times slower. This is quite typical performance. The articlethat Geo linked is a good read.
如果您使用服务器虚拟机,它可以对其进行更多优化,因此速度仅慢 3-4 倍。这是很典型的表现。Geo 链接的文章很好读。
Normal instaniation took: 47ms
Reflecting instantiation took:140ms
But if you enable scalar replacement with -XX:+DoEscapeAnalysis then the JVM is able to optimize the regular instantiation away (it will be 0-15ms) but the reflective instantiation stays the same.
但是,如果您使用 -XX:+DoEscapeAnalysis 启用标量替换,则 JVM 能够优化常规实例化(它将是 0-15 毫秒),但反射实例化保持不变。
回答by mfx
- Reflection was very slow when first introduced, but has been sped up considerably in newer JREs
- Still, it might not be a good idea to use reflection in an inner loop
- Reflection-based code has low potential for JIT-based optimizations
- Reflection is mostly used in connecting loosely-coupled components, i.e. in looking up concrete classes and methods, where only interfaces are known: dependeny-injection frameworks, instantiating JDBC implementation classes or XML parsers. These uses can often be done once at system startup, so the small inefficiency don't matter anyway!
- 反射在首次引入时非常缓慢,但在较新的 JRE 中已大大加快
- 尽管如此,在内部循环中使用反射可能不是一个好主意
- 基于反射的代码对于基于 JIT 的优化的潜力很小
- 反射主要用于连接松散耦合的组件,即查找具体的类和方法,其中只知道接口:依赖注入框架、实例化 JDBC 实现类或 XML 解析器。这些用途通常可以在系统启动时完成一次,所以无论如何小低效都无关紧要!
回答by bob
@Tim Bender 's code give these results on my machine(jdk_1.8_45, os_x 10.10, i7, 16G):
@Tim Bender 的代码在我的机器上给出了这些结果(jdk_1.8_45、os_x 10.10、i7、16G):
Reflecting instantiation took:1139ms
Normal instaniation took: 4969ms
Reflecting instantiation took:1139ms
Normal instaniation took: 4969ms
so it seems on modern JVM, the reflection code will be optimized also well.
所以看起来在现代 JVM 上,反射代码也会得到很好的优化。