Accuracy of System.nanoTime() to measure time elapsed decreases after a call to Thread.sleep()
Asked Answered
A

3

3

I'm encountering a really unusual issue here. It seems that the calling of Thread.sleep(n), where n > 0 would cause the following System.nanoTime() calls to be less predictable.

The code below demonstrates the issue.

Running it on my computer (rMBP 15" 2015, OS X 10.11, jre 1.8.0_40-b26) outputs the following result:

Control: 48497
Random: 36719
Thread.sleep(0): 48044
Thread.sleep(1): 832271

On a Virtual Machine running Windows 8 (VMware Horizon, Windows 8.1, are 1.8.0_60-b27):

Control: 98974
Random: 61019
Thread.sleep(0): 115623
Thread.sleep(1): 282451

However, running it on an enterprise server (VMware, RHEL 6.7, jre 1.6.0_45-b06):

Control: 1385670
Random: 1202695
Thread.sleep(0): 1393994
Thread.sleep(1): 1413220

Which is surprisingly the result I expect.

Clearly the Thread.sleep(1) affects the computation of the below code. I have no idea why this happens. Does anyone have a clue?

Thanks!

public class Main {
    public static void main(String[] args) {
        int N = 1000;
        long timeElapsed = 0;
        long startTime, endTime = 0;

        for (int i = 0; i < N; i++) {
            startTime = System.nanoTime();
            //search runs here
            endTime = System.nanoTime();

            timeElapsed += endTime - startTime;
        }

        System.out.println("Control: " + timeElapsed);

        timeElapsed = 0;

        for (int i = 0; i < N; i++) {
            startTime = System.nanoTime();
            //search runs here
            endTime = System.nanoTime();

            timeElapsed += endTime - startTime;

            for (int j = 0; j < N; j++) {
                int k = (int) Math.pow(i, j);
            }
        }

        System.out.println("Random: " + timeElapsed);

        timeElapsed = 0;

        for (int i = 0; i < N; i++) {
            startTime = System.nanoTime();
            //search runs here
            endTime = System.nanoTime();

            timeElapsed += endTime - startTime;

            try {
                Thread.sleep(0);
            } catch (InterruptedException e) {
                break;
            }
        }

        System.out.println("Thread.sleep(0): " + timeElapsed);

        timeElapsed = 0;

        for (int i = 0; i < N; i++) {
            startTime = System.nanoTime();
            //search runs here
            endTime = System.nanoTime();

            timeElapsed += endTime - startTime;

            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                break;
            }
        }

        System.out.println("Thread.sleep(1): " + timeElapsed);
    }
}

Basically I'm running a search within a while-loop which takes a break every iteration by calling Thread.sleep(). I want to exclude the sleep time from the overall time taken to run the search, so I'm using System.nanoTime() to record the start and finishing times. However, as you notice above, this doesn't work well.

Is there a way to remedy this?

Thanks for any input!

Accessible answered 15/11, 2015 at 4:18 Comment(8)
Hi @TheLima, I understand what you mean. This is just an arbitrary demonstration of the issue I'm facing. You can place as much code in between as you'd like and you'll still notice the same result: that the Thread.sleep() does increase the time between two System.nanoTime() callsAccessible
Your code is executed in interpreted frame. Do you really want to measure the interpreter speed? Move the whole main body into the separate method and call it 100 times in a loop. The results of the consequent calls will be significantly different.Dilatometer
I've edited the OP to add important notes related to what's being done where; because without them there could be, and there was, confusion.......................and moved the startTime and endTime variables out of the loops, because making that many unnecessary allocations should be considered a capital sin. =PFathom
Thanks for the edit. Though in general it is better practice to keep the variables in their appropriate scope. In this instance, for example, startTime and endTime are not required past their contained loops. @TagirValeev, I don't quite understand what you're hinting at. The code, since people are misunderstanding (I thought abstracting the underlying issue from my code would help_ is up hereAccessible
@TheLima as I said as a downvote reason on your now-deleted answer, moving the variable declarations out of the loops literally makes no difference. These are primitive variables, there is no object allocation; even if they were non-primitive, you'd still be allocating a new object when you assign a new value. If you look at the byte code with the variables declared inside and outside the loop, it would be identical. The "captial sin" is making changes in the name of optimization without evidence.Eli
@AndyTurner The values are not storing themselves in thin air...There is overhead...And although I'm not specially interested (and therefore not specially knowledgeable) about the inner workings of Java primitives, I'm fairly confident there is allocation. But let's drop the off-topics, shall we?Fathom
@TheLima if you make a comment accusing the OP of "capital sin", and have the cheek to edit the OP's code (not just tweaking or formatting it, but actually changing it), it is entirely fair to call you out.Eli
@AndyTurner First of all, I did check the OP's code; Second, the OP's code was missing critical information, even if it is just a comment saying where his search gets executed (my edits). Third, Any critical improvement is acceptable, and "changing" the OP's code, instead of fixing it is usually bad. Fourth, the misunderstanding was made by this issue in his code, and that has already been fixed in the OP, and removed by me, so here are the killer questions: What exactly are you calling me out for? What problem will be fixed by this? Who is it going to help? Are you just mad bro?Fathom
M
3

This is a complex topic because the timers used by the JVM are highly CPU- and OS-dependent and also change with JVM versions (e.g. by using newer OS APIs). Virtual machines may also limit the CPU capabilities they pass through to guests, which may alter the choices in comparison to a bare metal setup.

On x86 the RDTSC instruction provides the lowest latency and best granularity of all clocks, but under some configurations it's not available or reliable enough as a time source.

On linux you should check kernel startup messages (dmesg), the tsc-related /proc/cpuinfo flags and the selected /sys/devices/system/clocksource/*/current_clocksource. The kernel will try to use TSC by default, if it doesn't there may be a reason for that.

For some history you may want to read the following, but note that some of those articles may be a bit dated, TSC reliability has improved a lot over the years:

Midpoint answered 15/11, 2015 at 10:59 Comment(0)
A
2

I can suggest at least two possible reasons of such behavior:

  1. Power saving. When executing a busy loop, CPU runs at its maximum performance state. However, after Thread.sleep it is likely to fall into one of power-saving states, with frequency and voltage reduced. After than CPU won't return to its maximum performance immediately, this may take from several nanoseconds to microseconds.
  2. Scheduling. After a thread is descheduled due to Thread.sleep, it will be scheduled for execution again after a timer event which might be related to the timer used for System.nanoTime.

In both cases you can't directly work around this - I mean Thread.sleep will also affect timings in your real application. But if the amount of useful work measured is large enough, the inaccuracy will be negligible.

Acetous answered 15/11, 2015 at 11:13 Comment(1)
Something to note: Intel's speed step can take hundreds of milliseconds. Intel is working on a new faster speed step to change frequencies in a few milliseconds.Engrossment
F
0

The inconsistencies probably arise not from Java, but from the different OSs and VMs "atomic-" or system- clocks themselves.

According to the official .nanoTime() documentation:

no guarantees are made except that the resolution is at least as good as that of currentTimeMillis()

source

...I can tell from personal knowledge that this is because in some OSs and VMs, the system itself doesn't support "atomic" clocks, which are necessary for higher resolutions. (I will post the link to source this information as soon as I find it again...It's been a long time.)

Fathom answered 15/11, 2015 at 5:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.