Tuesday, March 23, 2010

No time for Windows

I'm timing code on Windows and I notice calls take either 0ms or 15ms. Odd. A little research reveals the culprit: Windows. (Although apparently fixed in Windows 7.) How to address this?

If I am simply timing code I'll use System.nanoTime(), but in my case I am relying on a software clock buried deeply in a library which calls System.currentTimeMillis(). But I can fix that code!

I need to continue using wall-clock millis but I'd like actual millisecond granularity. My solution:

class ActualMillis {
    private final long millis = System.currentTimeMillis();
    private final long nanos = System.nanoTime();

    public long currentTimeMillis() {
        return millis + (System.nanoTime() - nanos) / 1000000;
    }
}

The idea here is to record a snapshot of "now" at construction, and then use offsets from the snapshot on query. This addresses two issues at once:

  1. Millis have wall-clock accuracy but 15ms precision.
  2. Nanos have no accuracy but better than 1ms precision.

But still some things bother me:

  • Will this drift over long periods of time? I ran a series of tests over a few minutes or less, and there was no drift, but I have not tried full day or multi-day runs.
  • The contract for System.nanoTime() is difficult. I believe dividing by 1,000,000 obliterates problems like backwards time, but I cannot prove it (although Java 7 seems to address this).
  • What happens on different hardware than mine, on different JVMs (1.6.0_18), on different versions of Windows or perhaps if I just hold my breath wrong? I really cannot say.
  • The voodoo factor. This code is non-obvious and needs explication. I do not trust explications.

Other solutions very welcome.

3 comments:

Unknown said...

Would it be reasonably straightforward to schedule an infrequent task to renormalize your baseline time?

Vladimir Dolzhenko said...

There is no guarantee that millis or nanos can be calculated in the same time and more over in the same order - so there is a time difference between them - positive or negative is possible, so mixing nanos and ms can produce suspicious values

Brian Oxley said...

@Vlad - For my case I am interested in tracking millis wallclock time but can tolerate +/-1 millis from the true wallclock time as long as I do not drift over the course of a day.

My solution definitely needs more testing, but seems better than relying only on System.currentTimeMillis() which has a 10 or 15 milli granularity.

Another approach (which I have not tried) is to find a better Windows API call than the JDK uses, and write some JNI or JNA around it.