Java反射性能问题
我知道有很多话题都在谈论反射性能。
即使官方 Java 文档也说反射速度较慢,但我有以下代码:
public class ReflectionTest {
public static void main(String[] args) throws Exception {
Object object = new Object();
Class<Object> c = Object.class;
int loops = 100000;
long start = System.currentTimeMillis();
Object s;
for (int i = 0; i < loops; i++) {
s = object.toString();
System.out.println(s);
}
long regularCalls = System.currentTimeMillis() - start;
java.lang.reflect.Method method = c.getMethod("toString");
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
s = method.invoke(object);
System.out.println(s);
}
long reflectiveCalls = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
method = c.getMethod("toString");
s = method.invoke(object);
System.out.println(s);
}
long reflectiveLookup = System.currentTimeMillis() - start;
System.out.println(loops + " regular method calls:" + regularCalls
+ " milliseconds.");
System.out.println(loops + " reflective method calls without lookup:"
+ reflectiveCalls+ " milliseconds.");
System.out.println(loops + " reflective method calls with lookup:"
+ reflectiveLookup + " milliseconds.");
}
}
我认为这不是一个有效的基准,但至少应该显示出一些差异。 我执行它等待看到反射正常调用比常规调用慢一点。
但这会打印出这样的内容:
100000 regular method calls:1129 milliseconds.
100000 reflective method calls without lookup:910 milliseconds.
100000 reflective method calls with lookup:994 milliseconds.
请注意,首先我在没有那堆系统输出的情况下执行了它,然后我意识到一些 JVM 优化只是让它运行得更快,所以我添加了这些 printls 来看看反射是否仍然更快。
没有 sysout 的结果是:
100000 regular method calls:68 milliseconds.
100000 reflective method calls without lookup:48 milliseconds.
100000 reflective method calls with lookup:168 milliseconds.
我在互联网上看到,在旧 JVM 上执行的相同测试使得没有查找的反射比常规调用慢两倍,并且速度低于新更新。 如果有人可以执行它并说我错了,或者至少告诉我是否有与过去不同的东西使它更快。
按照说明,我分开运行每个循环,结果是(没有系统输出)
100000 regular method calls:70 milliseconds.
100000 reflective method calls without lookup:120 milliseconds.
100000 reflective method calls with lookup:129 milliseconds.
I know there's a lot of topics talking about Reflection performance.
Even official Java docs says that Reflection is slower, but I have this code:
public class ReflectionTest {
public static void main(String[] args) throws Exception {
Object object = new Object();
Class<Object> c = Object.class;
int loops = 100000;
long start = System.currentTimeMillis();
Object s;
for (int i = 0; i < loops; i++) {
s = object.toString();
System.out.println(s);
}
long regularCalls = System.currentTimeMillis() - start;
java.lang.reflect.Method method = c.getMethod("toString");
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
s = method.invoke(object);
System.out.println(s);
}
long reflectiveCalls = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
for (int i = 0; i < loops; i++) {
method = c.getMethod("toString");
s = method.invoke(object);
System.out.println(s);
}
long reflectiveLookup = System.currentTimeMillis() - start;
System.out.println(loops + " regular method calls:" + regularCalls
+ " milliseconds.");
System.out.println(loops + " reflective method calls without lookup:"
+ reflectiveCalls+ " milliseconds.");
System.out.println(loops + " reflective method calls with lookup:"
+ reflectiveLookup + " milliseconds.");
}
}
That I don't think is a valid benchmark, but at least should show some difference.
I executed it waiting to see the reflection normal calls being a bit slower than regular ones.
But this prints this:
100000 regular method calls:1129 milliseconds.
100000 reflective method calls without lookup:910 milliseconds.
100000 reflective method calls with lookup:994 milliseconds.
Just for note, first I executed it without that bunch of sysouts, and then I realized that some JVM optimization are just making it goes faster, so I added these printls to see if reflection was still faster.
The result without sysouts are:
100000 regular method calls:68 milliseconds.
100000 reflective method calls without lookup:48 milliseconds.
100000 reflective method calls with lookup:168 milliseconds.
I saw over internet that the same test executed on old JVMs make the reflective without lookup are two times slower than regular calls, and that speed falls over new updates.
If anyone can execute it and say me I'm wrong, or at least show me if there's something different than the past that make it faster.
Following instructions, I ran every loop separated and the result are (without sysouts)
100000 regular method calls:70 milliseconds.
100000 reflective method calls without lookup:120 milliseconds.
100000 reflective method calls with lookup:129 milliseconds.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
切勿在同一“运行”中测试不同代码位的性能。 JVM 有各种优化,这意味着尽管最终结果相同,但内部执行方式可能有所不同。更具体地说,在测试期间,JVM 可能注意到您多次调用 Object.toString,并开始内联对 Object.toString 的方法调用。它可能已经开始执行循环展开。或者可能在第一个循环中进行了垃圾收集,但在第二个或第三个循环中没有。
为了获得更有意义但仍然不完全准确的图片,您应该将测试分成三个单独的程序。
我的计算机上的结果(没有打印,每个运行 1,000,000 次)
所有三个循环都在同一程序中运行
循环在单独的程序中运行
Never performance test different bits of code in the same "run". The JVM has various optimisations that mean it though the end result is the same, how the internals are performed may differ. In more concrete terms, during your test the JVM may have noticed you are calling Object.toString a lot and have started to inline the method calls to Object.toString. It may have started to perform loop unfolding. Or there could have been a garbage collection in the first loop but not the second or third loops.
To get a more meaningful, but still not totally accurate picture you should separate your test into three separate programs.
The results on my computer (with no printing and 1,000,000 runs each)
All three loops run in same program
Loops run in separate programs
Brian Goetz 撰写的关于微基准测试的文章值得一读。在进行测量之前,您似乎没有做任何事情来预热 JVM(这意味着给它一个机会做任何内联或其他优化),所以在进行测量之前,非反射测试很可能仍未预热 -还没有增加,这可能会扭曲你的数字。
There's an article by Brian Goetz on microbenchmarks that's worth reading. It looks like you're not doing anything to warm up the JVM (meaning give it a chance to do whatever inlining or other optimizations it's going to do) before doing your measurements, so it's likely the non-reflective test is still not warmed-up yet, and that could skew your numbers.
当您有多个长时间运行的循环时,第一个循环可以触发方法进行编译,从而从一开始就优化后面的循环。然而,优化可能不是最优的,因为它没有这些循环的运行时信息。 toString 相对昂贵,并且比反射调用花费的时间更长。
您不需要单独的程序来避免由于较早的循环而优化循环。您可以用不同的方法运行它们。
我得到的结果是
代码
When you have multiple long running loops, the first loop can trigger the method to compile resulting in the later loops being optimised from the start. However the optimisation can be sub-optimal as it has no runtime information for those loops. The toString is relatively expensive and couple be taking longer than the reflections calls.
You don't need separate programs to avoid loop being optimised due to an earlier loop. You can run them in different methods.
The results I get are
The code
像这样的微基准永远不会准确——当虚拟机“预热”时,它会内联代码段并在运行过程中优化代码段,因此在程序中执行 2 分钟的同样的事情可能会极大地提高性能。一开始就超越它。
就这里发生的情况而言,我的猜测是第一个“正常”方法调用块将其预热,因此反射块(实际上是所有后续调用)会更快。我可以看到,通过反射调用方法所增加的唯一开销是查找指向该方法的指针,无论如何,这是一个纳秒级的操作,并且很容易被 JVM 缓存。剩下的就是虚拟机如何预热,即到达反射调用时。
Micro-benchmarks like this are never going to be accurate at all - as the VM "warms up" it'll inline bits of code and optimise bits of code as it goes along, so the same thing executed 2 minutes into a program could vastly outperform it right at the start.
In terms of what's happening here, my guess is that the first "normal" method call block warms it up, so the reflective blocks (and indeed all subsequent calls) would be faster. The only overhead added through reflectively calling a method that I can see is looking up the pointer to that method, which is a nanosecond-scale operation anyway and would be easily cached by the JVM. The rest would be on how the VM is warmed up, which it is by the time you reach the reflective calls.
反射调用比普通调用慢并没有什么内在原因。 JVM可以将它们优化成相同的东西。
实际上,人力资源是有限的,首先要优化正常的通话。随着时间的推移,他们可以致力于优化反射调用;尤其是当反思变得越来越流行时。
There is no inherent reason why reflective call should be slower than a normal call. JVM can optimize them into the same thing.
Practically, human resources are limited, and they had to optimize normal calls first. As time passes by they can work on optimizing reflective calls; especially when reflection becomes more and more popular.
让我印象深刻的是,您在内部基准循环中放置了“System.out.println(s)”调用。
由于执行 IO 必然会很慢,因此它实际上“吞噬”了您的基准测试,并且调用的开销变得可以忽略不计。
尝试删除“println()”调用并运行这样的代码,我相信您会对结果感到惊讶(需要一些愚蠢的计算来避免编译器完全优化调用):
-- TR
It strikes me that you have placed a "System.out.println(s)" call inside your inner benchmark loop.
Since performing IO is bound to be slow, it actually "swallows up" your benchmark and the overhead of the invoke becomes negligible.
Try removing the "println()" call and running code like this, I'm sure you'd be surprised by the result (some of the silly calculations are needed to avoid the compiler optimizing away the calls altogether):
-- TR
我一直在编写自己的微基准,没有循环,并且使用 System.nanoTime():
我已经在我的机器上的 Eclipse 中执行了十几次,结果差别很大,但这是我通常得到的结果:
现在,不要忘记 Nathan 的回复中描述的关于微基准的警告 - 该微基准肯定存在很多缺陷 - 如果他们说反射比直接调用慢很多,请相信文档。
I have been writing my own micro-benchmark, without loops, and with
System.nanoTime()
:I have been executing that in Eclipse on my machine a dozen times, and the results vary quite a bit, but here is what I typically get:
Now, do not forget the caveats on microbenchmarks described in Nathan's reply - there are certainly a lot of flaws in that micro benchmark - and trust the documentation if they say that reflection is a LOT slower than direct invocation.
即使您在两种情况下查找方法(即在第二次和第三次循环之前),
第一次查找比第二次查找花费的时间要少,这应该是相反的,并且比我的机器上的常规方法调用要少。
尽管如此,如果您将第二个循环与方法查找和 System.out.println 语句一起使用,我会得到:
没有 System.out.println 语句,我得到:
Even if you look up the method in both cases (i.e. before 2nd and 3rd loop),
the first lookup takes way less time than the second lookup, which should have been the other way around and less than a regular method call on my machine.
Neverthless, if you use the 2nd loop with method lookup, and
System.out.println
statement, I get this:Without
System.out.println
statement, I get: