how to make sure no jvm and compiler optimization occurs
我有这个代码测试
1 2 3 4 5 6 7 8 9 | long before = getTimeInMilli(); for (int i = 0; i < TIMES_TO_ITERATE; i++) { long before1 = getTimeInMilli(); doSomeReallyHardWork(); long after1 = getTimeInMilli(); } long after = getTimeInMilli(); System.out.println(getClass().getSimpleName() +" total is" + (after - before)); |
我想确保没有JVM或编译器优化发生,因此测试将是有效的并且实际上会显示出差异。
怎么样?
编辑:我改变了代码示例,以便更清楚。 我在这里检查的是在不同的实现中调用getTimeInMilli()需要多长时间 - Calendar vs System。
我想你需要禁用JIT。添加到您的运行命令下一个选项:
1 | -Djava.compiler=NONE |
您希望优化发生,因为它将在现实生活中 - 如果JVM没有以与您感兴趣的实际情况相同的方式进行优化,则测试将无效。
但是,如果你想确保JVM没有删除它可能会考虑no-ops的调用,否则一个选项是使用结果 - 所以如果你反复调用
请注意,您可能仍然有一些偏见 - 例如,如果JVM可以廉价地确定自上次调用
还有一件事需要考虑:假设你想要模拟一个代码运行很多的真实世界的情况,你应该在采取任何时间之前运行很多代码 - 因为Hotspot JVM将逐步更加优化,并且可能你关心的是高度优化的版本,不想测量JITting的时间和代码的"慢"版本。
正如斯蒂芬提到的,你几乎肯定会把时间安排在循环之外......并且不要忘记实际使用结果......
对不起,但你想要做的事情没什么意义。
如果关闭JIT编译,那么您只需要测量在关闭JIT编译的情况下调用该方法所需的时间。这不是有用的信息...因为它告诉你几乎没有关于打开JIT编译时会发生什么的信息。
JIT开启和关闭之间的时间可能因人而异。在JIT关闭的情况下,你不太可能想要在生产中运行任何东西。
更好的方法是这样做:
1 2 3 4 5 | long before1 = getTimeInMilli(); for (int i = 0; i < TIMES_TO_ITERATE; i++) { doSomeReallyHardWork(); } long after1 = getTimeInMilli(); |
...和/或使用纳秒时钟。
如果您要测量调用
1 2 3 4 5 6 7 8 9 10 | public long test() { long before1 = getTimeInMilli(); long sum = 0; for (int i = 0; i < TIMES_TO_ITERATE; i++) { sum += getTimeInMilli(); } long after1 = getTimeInMilli(); System.out.println("Took" + (after - before) +" milliseconds"); return sum; } |
......并多次打电话,直到印刷的时间稳定下来。
无论哪种方式,我的主要观点仍然存在,转向JIT编译和/或优化将意味着你正在测量一些对知道无用的东西,而不是你真正想要找到的东西。 (除非,也就是说,你打算在关闭JIT的情况下运行你的应用程序......我很难相信......)
您正在做什么看起来像基准测试,您可以阅读Robust Java基准测试,以获得有关如何使其正确的良好背景。简而言之,您不需要将其关闭,因为它不会是生产服务器上发生的事情。相反,您需要知道关闭可能的"实时"估计/性能。在优化之前,您需要"预热"您的代码,它看起来像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // warm up for (int j = 0; j < 1000; j++) { for (int i = 0; i < TIMES_TO_ITERATE; i++) { long before1 = getTimeInMilli(); doSomeReallyHardWork(); long after1 = getTimeInMilli(); } } // measure time long before = getTimeInMilli(); for (int j = 0; j < 1000; j++) { for (int i = 0; i < TIMES_TO_ITERATE; i++) { long before1 = getTimeInMilli(); doSomeReallyHardWork(); long after1 = getTimeInMilli(); } } long after = getTimeInMilli(); System.out.prinltn("What to expect?" + (after - before)/1000 ); // average time |
当我们测量代码的性能时,我们使用这种方法,它使我们的代码需要更少的实时工作。在分离方法中测量代码更好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public void doIt() { for (int i = 0; i < TIMES_TO_ITERATE; i++) { long before1 = getTimeInMilli(); doSomeReallyHardWork(); long after1 = getTimeInMilli(); } } // warm up for (int j = 0; j < 1000; j++) { doIt() } // measure time long before = getTimeInMilli(); for (int j = 0; j < 1000; j++) { doIt(); } long after = getTimeInMilli(); System.out.prinltn("What to expect?" + (after - before)/1000 ); // average time |
第二种方法更精确,但它也取决于VM。例如。 HotSpot可以执行"堆栈内替换",这意味着如果方法的某些部分经常执行,它将由VM优化,旧版本的代码将在执行方法时与优化的代码交换。当然,它需要VM端的额外操作。 JRockit不会这样做,只有在再次执行此方法时才会使用优化版本的代码(因此没有'运行时'优化...我的意思是在我的第一个代码示例中,所有旧代码都将被执行...除了
更新:我在回答时编辑了有问题的代码;)