我有以下两个程序:
long startTime = System.currentTimeMillis();
for (int i = 0; i < N; i++);
long endTime = System.currentTimeMillis();
System.out.println("Elapsed time: " + (endTime - startTime) + " msecs");
和
long startTime = System.currentTimeMillis();
for (long i = 0; i < N; i++);
long endTime = System.currentTimeMillis();
System.out.println("Elapsed time: " + (endTime - startTime) + " msecs");
注意:唯一的区别是循环变量的类型(int
和long
)。
当我运行它时,无论N
的值如何,第一个程序始终打印0到16毫秒。第二个需要更长的时间。对于N == Integer.MAX_VALUE
,它在我的机器上运行大约1800毫秒。运行时间在N
中似乎或多或少是线性的。
那为什么会这样?
我认为JIT编译器将int
循环优化为死亡。并且有充分的理由,因为显然它什么都不做。但是为什么不对long
循环也这样做呢?
一位同事认为我们可能正在测量JIT编译器在long
循环中的工作,但由于N
中的运行时似乎是线性的,所以情况可能并非如此。 / p>
我正在使用JDK 1.6.0更新17:
C:\>java -version
java version "1.6.0_17"
Java(TM) SE Runtime Environment (build 1.6.0_17-b04)
Java HotSpot(TM) 64-Bit Server VM (build 14.3-b01, mixed mode)
我使用的是Windows XP Professional x64 Edition,Service Pack 2,配备2.40GHz的Intel Core2 Quad CPU。
声明
我知道微基准测试在生产中没用。我也知道System.currentTimeMillis()
并不像它的名字那样准确。这只是我在鬼混时注意到的事情,我只是好奇为什么会发生这种情况;仅此而已。
答案 0 :(得分:5)
这是一个有趣的问题,但说实话,我不相信考虑Hotspot在这里的行为会产生有用的信息。你得到的任何答案都不会在一般情况下转移(因为我们正在研究Hotspot在某种特定情况下执行的优化),所以它们会帮助你理解为什么一个no-op比另一个更快, 但它们无法帮助您编写更快的“真实”程序。
在这类事情上撰写非常容易误导的微观基准也非常容易 - 请参阅this IBM DW article了解一些常见的陷阱,如何避免它们以及对你正在做的事情的一些一般性评论。
所以这真的是一个“无评论”的答案,但我认为这是唯一有效的答案。编译时平凡的无操作循环 不需要快,因此编译器在某些条件下不会被优化为快速。
答案 1 :(得分:4)
您可能正在使用32位JVM。结果可能与64位JVM不同。在32位JVM中,int可以映射到本机32位整数,并通过单个操作递增。同样不能持续很长时间,这将需要更多的操作来增加。
有关int和long size的讨论,请参阅此question。
答案 2 :(得分:3)
我的猜测 - 这只是猜测 - 是这样的:
JVM得出结论,第一个循环实际上什么都不做,所以它完全删除它。没有变量从for循环“逃脱”。
在第二种情况下,循环也没有做任何事情。但可能是确定循环不执行任何操作的JVM代码具有“if(type of i)== int”子句。在这种情况下,删除do-nothing for-loop的优化仅适用于int。
删除代码的优化必须确保没有副作用。 JVM程序员似乎在谨慎方面犯了错误。
答案 3 :(得分:1)
另请注意,使用System.currentTimeMillis()
获得的系统时钟值在所有操作系统上的分辨率均不为1 ms。你无法使用它来非常准确地计算持续时间非常短的事件。
看一下这篇文章,解释为什么用Java编写微基准测试并不像大多数人想象的那么简单:Anatomy of a flawed microbenchmark