Time::currentTimeMillis() goes back in time (sometimes)


#1

I have a bit of code that takes a timestamp with

const int64 start = Time::currentTimeMillis();

at some time, and then measures the time elapsed a few lines below with

const int64 delta = Time::currentTimeMillis() - start;

The code between the two calls is not relevant, suffice to say that it could take from a few seconds down to nearly nothing.

In consideration of that, I just assumed that delta would have been ranging from a few thousands down to 0 at minimum.

jassert (delta >= 0);

imagine my surprise when, seemingly at random times and only while letting the code run for many minutes, the above assertion was being hit, for apparently no reason.

After some thinking, I found the cause: the clock on my computer is set to be regularly updated with some internet time server. Also, it tends to run faster than actual/server time, so each of those regular updates actually sets it some seconds backwards. When said update happens between the two calls to Time::currentTimeMillis() above, I end up with a negative value of delta because the second call returned a value that's few seconds "before" the time represented by start.

Now the actual question: since I think this is a quite common pattern, and equally common is the assumption that the value returned by Time::currentTimeMillis() is expected to only grow in value between calls, what do you think about tweaking Time::currentTimeMillis() so that it only returns non-decreasing numbers, i.e. it will never yield a value that's lower than a value that was returned by a previous call?

(with a static value inside the function to hold the latest returned value, the implementation of this could be far easier than the description of the potential and counterintuitive issue in the documentation of the function itself)

 

 


#2

If this is Windows, we recently fixed something that may help.


#3

err.. no, it's on a Mac..


#4

Does Time::getCurrentTime().toMilliseconds(); also slip? Are they the same thing?

 I'm using:

float elapsed_s() {
    static int64 t0 = Time::getCurrentTime().toMilliseconds();
    return (Time::getCurrentTime().toMilliseconds() - t0) / 1000.f;
}

 How about http://www.juce.com/doc/classPerformanceCounter as an alternative?

IIRC performance counters retrieve CPU cycles and convert back to ms, although I don't know how these play out with modern computers that dynamically change CPU speed...

π


#5

This is a reason to always question the data where it is coming from...

You can fix that on various places: Your ntp client is able to avoid jumps. On Linux and Macs see man ntpdate:

-B 

Force the time to always be slewed using the adjtime(2) system call, even if the measured offset is greater than +-128 ms.  The default is to step the time using settimeofday(2) if the offset is greater than +-128 ms.  Note that, if the offset is much greater than +-128 ms in this case, it can take a long time (hours) to slew the clock to the correct value.  During this time, the host should not be used to synchronize clients.

Unfortunately this option is not accessible via the system configuration panel and as a developer you should not push users to change their setup without a strong cause.

If you are doing audio, the most precise time source is probably the audio driver, if you simply count the processBlock or getNextAudioBlock and multiply with the buffer size divided by samplerate (remember the blocksize may vary during runtime as stated on several places in the docs). Put that into an atomic int64 and you have the most precise time as long as your audio is not interrupted.

Also see the AudioPlayHead: http://www.juce.com/doc/classAudioPlayHead

 

 


#6

Just skimming through the getting started book http://www.juce.com/forum/topic/getting-started-juce-book:

Measuring and displaying time

The JUCE Time class provides a cross-platform way to specify, measure, and format date and time information in a human-readable fashion. Internally, the Time class stores a value in milliseconds relative to midnight on 1st January 1970. To create a Time object that represents the current time, use Time::getCurrentTime() like the following:

Time now = Time::getCurrentTime();

To bypass the creation of the Time object, you can access the millisecond counter as a 64-bit value directly:

int64 now = Time::currentTimeMillis();

The Time class also provides access to a 32-bit millisecond counter that measures time since system startup:

uint32 now = Time::getMillisecondCounter();

The important point to note about Time::getMillisecondCounter() is that it is independent of the system time, and would be unaffected by changes to the system time either by the user changing the time, changes due to national daylight saving, and so on.


Both the Time::getCurrentTime() and Time::getMillisecondCounter() functions have a similar accuracy, which is within a few milliseconds on most platforms. However, the Time class also provides access to a higher resolution counter that returns values as a double precision (64-bit) oating-point value.
This function is Time::getMillisecondCounterHiRes(), and is also relative to the system start-up as is the value returned from the Time::getMillisecondCounter() function.


 As far as I can see it is correct that Time::currentTimeMillis() is a erratic, as it should be reflecting system time, which is probably going to be erratic.

If you need consistent timing, Time::getMillisecondCounter() or Time::getMillisecondCounterHiRes() appear to be the right tools for the job.

Note: http://www.juce.com/doc/classPerformanceCounter uses Time::getHighResolutionTicks() (https://github.com/julianstorer/JUCE/blob/master/modules/juce_core/time/juce_PerformanceCounter.cpp)

π


#7

Thank you all for answering and making suggestions.

I know about the other more precise alternatives, my use case doesn't really need any of that precision.

The whole point of my original post was:

Watch out when measuring intervals with Time::currentTimeMillis() because at the present moment they can turn out to be negative (because of time server updates happening between calls).
Since that's a very counterintuitive thing to happen, I questioned about the opportunity of either change the code so that it doesn't happen (subsequent calls could be made to always return non-decreasing values) or state it clearly in the documentation as a note, just to remind the developer of the odd case.


#8

Well, yes, Time::currentTimeMillis() is not meant to be a steady clock (i.e. a clock that never goes backwards). It just returns whatever the OS considers its current time at the moment. It is not meant to be used for measuring intervals. I added this info to the documentation to make it clear.

As pi already pointed out, for measuring intervals you should be doing getMillisecondCounter() or getMillisecondCounterHiRes() instead, depending on whether you need high resolution or not.

An alternative is to use the C++ standard library, which by the way makes the same distinction:

std::chrono::system_clock (just to get the current system time, allowed to be erratic)
std::chrono::steady_clock (for measuring time intervals)
std::chrono::high_resolution_clock (same as above but hi-res)