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

发布于 2024-10-21 13:17:23 字数 639 浏览 3 评论 0原文

我有这段代码正在测试 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));

我想确保没有 JVM 或编译器优化发生,所以测试将是有效的并且会实际显示差异。

如何确定?

编辑:我更改了代码示例,使其更加清晰。我在这里检查的是在不同的实现中调用 getTimeInMilli() 需要多少时间 - CalendarSystem

I have this code that is testing Calendar.getInstance().getTimeInMillis() vs 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.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

半葬歌 2024-10-28 13:17:23

我认为你需要禁用 JIT。添加到您的运行命令下一个选项:

-Djava.compiler=NONE

I think you need to disable JIT. Add to your run command next option:

-Djava.compiler=NONE
弥繁 2024-10-28 13:17:23

希望进行优化,因为它会在现实生活中发生 - 如果 JVM 没有按照您感兴趣的实际情况进行优化,则测试将无效 但是,

如果您想确保 JVM 不会删除可能会考虑无操作的调用,一种选择是使用结果 - 因此,如果您正在调用 System.currentTimeMillis()< 。 /code> 重复,您可以对所有返回值求和,然后在最后显示总和。

请注意,您可能仍然存在一些偏差 - 例如,如果 JVM 可以轻松地确定自上次调用 System.currentTimeMillis() 以来只过去了一小部分时间,则可能会有一些优化,因此它可以使用缓存值。我并不是说这里的情况实际上是这样,但这是你需要考虑的事情。最终,基准测试只能真正测试您给予它们的负载。

另一件需要考虑的事情是:假设您想要模拟代码运行很多的真实情况,那么您应该在采取任何计时之前运行代码很多次 - 因为 Hotspot JVM 会逐渐优化,并且大概您关心高度优化的版本,并且不想想要测量 JITting 和代码的“慢速”版本的时间。

正如 Stephen 提到的,您几乎肯定应该将计时置于循环之外...并且不要忘记实际使用结果...

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.

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.

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

As Stephen mentioned, you should almost certainly take the timing outside the loop... and don't forget to actually use the results...

本宫微胖 2024-10-28 13:17:23

您正在做的事情看起来像基准测试,您可以阅读 Robust Java benchmarking 获得一些有关如何正确实施的良好背景知识。简而言之,您不需要将其关闭,因为它不会是生产服务器上发生的情况。相反,您需要知道“实时”估计/性能的接近程度。在优化之前,您需要“预热”代码,如下所示:

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

当我们使用这种方法测量代码性能时,它为我们提供了更少的代码运行所需的实时时间。更好地在单独的方法中测量代码:

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 优化,并且在方法执行时旧版本的代码将与优化的代码交换。当然,这需要虚拟机方面的额外操作。 JRockit 不会这样做,仅当再次执行此方法时才会使用代码的优化版本(因此没有“运行时”优化...我的意思是在我的第一个代码示例中,所有时间旧代码都会被执行...除了doSomeReallyHardWork 内部 - 它们不属于此方法,因此优化效果很好)。

更新:有问题的代码在我回答时被编辑;)

What you are doing looks like benchmarking, you can read Robust Java benchmarking to get some good background about how to make it right. 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. E.g. 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. Of course it takes extra actions from VM side. 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).

UPDATED: code in question was edited while I was answering ;)

独行侠 2024-10-28 13:17:23

抱歉,但是您尝试做的事情毫无意义。

如果您关闭 JIT 编译,那么您只需测量在 JIT 编译关闭的情况下调用该方法需要多长时间。这不是有用的信息...因为它几乎没有告诉您有关打开 JIT 编译时会发生什么的信息1

JIT 开启和关闭之间的时间可能相差很大。您不太可能希望在关闭 JIT 的情况下在生产中运行任何内容。

更好的方法是这样做:

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

...和/或使用纳秒时钟。


如果您尝试测量调用两个版本的 getTimeInMillis() 所花费的时间,那么我不明白您调用 doSomeReallyHardWork() 的意义。一个更明智的基准是这样的:

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 的情况下在生产中运行您的应用程序...我觉得很难相信...)


1 - 我注意到有人评论说关闭 JIT 编译允许他们轻松演示类的 O(1)O(N)O(N^2) 算法之间的差异。但我反驳说,最好学习如何 编写正确的微基准。出于严肃的目的,您需要学习如何以数学方式推导算法的复杂性。即使有完美的基准测试,您也可能通过尝试从性能测量中“推断”复杂性来得到错误的答案。 (以HashMap的行为为例。)

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. This is not useful information ... because it tells you little if anything about what will happen when JIT compilation is turned on1.

The times between JIT on and off can be different by a huge factor. You are unlikely to want to run anything in production with JIT turned off.

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(). 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. (Unless, that is, you are intending to run your application in production with JIT turned off ... which I find hard to believe ...)


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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文