简体   繁体   English

如何禁用编译器和 JVM 优化?

[英]How to disable compiler and JVM optimizations?

I have this code that is testing Calendar.getInstance().getTimeInMillis() vs System.currentTimeMilli() :我有这段代码正在测试Calendar.getInstance().getTimeInMillis()System.currentTimeMilli()

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));

I want to make sure no JVM or compiler optimization happens, so the test will be valid and will actually show the difference.我想确保没有 JVM 或编译器优化发生,所以测试将是有效的并且会实际显示差异。

How to be sure?怎么确定?

EDIT : I changed the code example so it will be more clear.编辑:我更改了代码示例,因此会更清楚。 What I am checking here is how much time it takes to call getTimeInMilli() in different implementations - Calendar vs System .我在这里检查的是在不同的实现中调用getTimeInMilli()需要多少时间 - CalendarSystem

I think you need to disable JIT.我认为您需要禁用 JIT。 Add to your run command next option:添加到您的运行命令下一个选项:

-Djava.compiler=NONE

You want optimization to happen, because it will in real life - the test wouldn't be valid if the JVM didn't optimize in the same way that it would in the real situation you're interested in.希望进行优化,因为它会在现实生活中发生 - 如果 JVM 的优化方式与您感兴趣的实际情况不同,则测试将无效。

However, if you want to make sure that the JVM doesn't remove calls that it could potentially consider no-ops otherwise, one option is to use the result - so if you're calling System.currentTimeMillis() repeatedly, you might sum all the return values and then display the sum at the end.但是,如果您想确保 JVM 不会删除它可能会考虑无操作的调用,一种选择是使用结果 - 因此,如果您重复调用System.currentTimeMillis() ,您可能会总结所有返回值,然后在最后显示总和。

Note that you may still have some bias though - for example, there may be some optimization if the JVM can cheaply determine that only a tiny amount of time has passed since the last call to System.currentTimeMillis() , so it can use a cached value.请注意,您可能仍然有一些偏见 - 例如,如果 JVM 可以廉价地确定自上次调用System.currentTimeMillis()以来只过去了System.currentTimeMillis()时间,那么可能会有一些优化,因此它可以使用缓存价值。 I'm not saying that's actually the case here, but it's the kind of thing you need to think about.我并不是说这里的情况确实如此,但这是您需要考虑的事情。 Ultimately, benchmarks can only really test the loads you give them.最终,基准测试只能真正测试您提供给它们的负载。

One other thing to consider: assuming you want to model a real world situation where the code is run a lot , you should run the code a lot before taking any timing - because the Hotspot JVM will optimize progressively harder, and presumably you care about the heavily-optimized version and don't want to measure the time for JITting and the "slow" versions of the code.另一件需要考虑的事情:假设你想模拟一个真实世界的情况,其中代码运行很多,你应该在进行任何计时之前运行代码 - 因为 Hotspot JVM 将逐步优化,并且大概你关心重优化的版本,不想衡量JITting和代码的“慢”版本的时间。

As Stephen mentioned, you should almost certainly take the timing outside the loop... and don't forget to actually use the results...正如斯蒂芬提到的,您几乎肯定应该将时间安排在循环之外……并且不要忘记实际使用结果……

What you are doing looks like benchmarking, you can read Robust Java benchmarking to get some good background about how to make it right.您正在做的事情看起来像基准测试,您可以阅读Robust Java benchmarking以获取有关如何使其正确的良好背景知识。 In few words, you don't need to turn it off, because it won't be what happens on production server.. instead you need to know the close the possible to 'real' time estimation / performance.简而言之,您不需要关闭它,因为它不会发生在生产服务器上。相反,您需要知道接近“实时”估计/性能的可能性。 Before optimization you need to 'warm up' your code, it looks like:在优化之前,您需要“预热”您的代码,它看起来像:

// 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

When we measure performance of our code we use this approach, it give us more less real time our code needs to work.当我们测量代码的性能时,我们使用这种方法,它使我们的代码需要工作的实时性更少。 Even better to measure code in separated methods:更好地以分离的方法测量代码:

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

Second approach is more precise, but it also depends on VM.第二种方法更精确,但也取决于 VM。 Eg HotSpot can perform "on-stack replacement" , it means that if some part of method is executed very often it will be optimized by VM and old version of code will be exchanged with optimized one while method is executing.例如,HotSpot 可以执行“栈上替换” ,这意味着如果方法的某些部分被频繁执行,它将被 VM 优化,并且在方法执行时旧版本的代码将与优化的代码交换。 Of course it takes extra actions from VM side.当然,它需要从 VM 端采取额外的行动。 JRockit does not do it, optimized version of code will be used only when this method is executed again (so no 'runtime' optimization... I mean in my first code sample all the time old code will be executed... except for doSomeReallyHardWork internals - they do not belong to this method, so optimization will work well). JRockit 不这样做,只有在再次执行此方法时才会使用优化版本的代码(因此没有“运行时”优化……我的意思是在我的第一个代码示例中,所有时间都将执行旧代码……除了doSomeReallyHardWork内部结构 - 它们不属于此方法,因此优化将运行良好)。

UPDATED: code in question was edited while I was answering ;)更新:有问题的代码在我回答时被编辑;)

Sorry, but what you are trying to do makes little sense.抱歉,但是您尝试做的事情毫无意义。

If you turn off JIT compilation, then you are only going to measure how long it takes to call that method with JIT compilation turned off .如果关闭JIT编译,那么你只打算来衡量它需要多长时间来调用与JIT编译该方法关闭 This is not useful information ... because it tells you little if anything about what will happen when JIT compilation is turned on 1 .这不是有用的信息……因为它几乎没有告诉您打开 JIT 编译时会发生什么1

The times between JIT on and off can be different by a huge factor. JIT 开启和关闭之间的时间可能相差很大。 You are unlikely to want to run anything in production with JIT turned off.您不太可能希望在关闭 JIT 的情况下在生产中运行任何东西。

A better approach would be to do this:更好的方法是这样做:

long before1 = getTimeInMilli();
for (int i = 0; i < TIMES_TO_ITERATE; i++) {
    doSomeReallyHardWork();
}
long after1 = getTimeInMilli();

... and / or use the nanosecond clock. ...和/或使用纳秒时钟。


If you are trying to measure the time taken to call the two versions of getTimeInMillis() , then I don't understand the point of your call to doSomeReallyHardWork() .如果您试图测量调用getTimeInMillis()的两个版本所花费的时间,那么我不明白您调用doSomeReallyHardWork() A more senible benchmark would be this:一个更明智的基准是这样的:

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;
}

... and call that a number of times, until the times printed stabilize. ...并多次调用,直到打印的时间稳定。

Either way, my main point still stands, turning of JIT compilation and / or optimization would mean that you were measuring something that is not useful to know, and not what you are really trying to find out.无论哪种方式,我的主要观点仍然存在,转向 JIT 编译和/或优化将意味着您正在测量一些不需要知道的东西,而不是您真正想要找出的东西。 (Unless, that is, you are intending to run your application in production with JIT turned off ... which I find hard to believe ...) (除非,也就是说,您打算在关闭 JIT 的情况下在生产环境中运行您的应用程序……我觉得难以置信……)


1 - I note that someone has commented that turning off JIT compilation allowed them to easily demonstrate the difference between O(1) , O(N) and O(N^2) algorithms for a class. 1 - 我注意到有人评论说关闭 JIT 编译允许他们轻松演示O(1)O(N)O(N^2)算法之间的差异。 But I would counter that it is better to learn how to write a correct micro-benchmark .但我会反驳说,最好学习如何编写正确的微基准测试 And for serious purposes, you need to learn how to derive the complexity of the algorithms ... mathematically.出于严肃的目的,您需要学习如何从数学上推导出算法的复杂性。 Even with a perfect benchmark, you can get the wrong answer by trying to "deduce" complexity from performance measurements.即使使用完美的基准测试,您也可能通过尝试从性能测量中“推断”复杂性而得到错误的答案。 (Take the behavior of HashMap for example.) (以HashMap的行为为例。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM