setInterval timing slowly drifts away from staying accurate

It seems that when I setInterval for 1000ms, it actually fires the function every 1001ms or so. This results in a slow temporal drift the longer its running.

var start;
var f = function() {
    if (!start) start = new Date().getTime();
    var diff = new Date().getTime() - start;
    var drift = diff % 1000;
    $('<li>').text(drift + "ms").appendTo('#results');
};

setInterval(f, 1000);

When run this shows the inaccuracy immediately.

  • 0ms
  • 1ms
  • 2ms
  • 3ms
  • 4ms
  • 5ms
  • 5ms
  • 7ms
  • 8ms
  • 9ms
  • 9ms
  • 10ms

See it for yourself: http://jsfiddle.net/zryNf/

So is there a more accurate way to keep time? or a way to make setInterval behave with more accuracy?

Answers:

Answer

I think I may have figured out a solution. I figured, if you can measure it you can compensate for it, right?

http://jsfiddle.net/zryNf/9/

var start;
var nextAt;

var f = function() {
    if (!start) {
        start = new Date().getTime();
        nextAt = start;
    }
    nextAt += 1000;

    var drift = (new Date().getTime() - start) % 1000;    
    $('<li>').text(drift + "ms").appendTo('#results');

    setTimeout(f, nextAt - new Date().getTime());
};

f();

result varies a bit but here's a recent run:

0ms
7ms
2ms
1ms
1ms
1ms
2ms
1ms
1ms
1ms

So if it gets called 1ms, 2ms or even 10ms later than it should the next call is scheduled to compensate for that. As long as inaccuracy is only per call, but the clock should never lose time, then this should work well.


And now I wrapped this up a global accurateInterval function which is a near drop in replacement for setInterval. https://gist.github.com/1d99b3cd81d610ac7351

Answer

with a bit of googleing, you will see thatsetInterval and settimeout both will not execute the code at the exact specified time you tell it. with setInterval(f,1000); it will wait AT LEAST 1000MS before it executes, it will NOT wait exactly 1000MS. Other processes are also waiting for their turn to use the CPU, which causes delays. If you need an accurate timer that times at 1 second. I would use a shorter interval, like 50MS and compare it to the start time. I wouldnt go under 50MS though because browsers have a minimum interval

here are a few references:

"In order to understand how the timers work internally there's one important concept that needs to be explored: timer delay is not guaranteed. Since all JavaScript in a browser executes on a single thread asynchronous events (such as mouse clicks and timers) are only run when there's been an opening in the execution. This is best demonstrated with a diagram, like in the following:" taken from: http://css.dzone.com/news/how-javascript-timers-work

"Chrome and Chromium provide an interval that averages just over 41 milliseconds, enough of a difference for the second clock to be visibly slower in well under a minute. Safari comes in at just under 41ms, performing better than Chrome, but still not great. I took these readings under Windows XP, but Chrome actually performed worse under Windows 7 where the interval averaged around 46ms." taken from: http://www.goat1000.com/2011/03/23/how-accurate-is-window.setinterval.html

Answer

I don't see a drift nearly as large as your script is reporting:
http://jsfiddle.net/hqmLg/1/

I'm leaving that script running. Right now (Chrome, Win 7) I see:

240 calls in 240.005s is 0.99979 calls/second

Indeed, I've seen the drift go up to .007s and then down to .003s. I think your measurement technique is flawed.

In Firefox I see it drift even more strongly (+/- 8ms either direction) and then compensate in the next run. Most of the time I'm seeing "1.000000 calls/second" in Firefox.

Answer

Here's another autocorrecting interval. The interval is set to a shorter time period and then it waits until it's at least a second later to fire. It won't always fire exactly 1000ms later (seems to range from 0-6ms delay), but it autocorrects and won't drift.

EDIT: Updated to use recalling setTimeout instead of setInterval otherwise it may do something odd after 1000 or so iterations.

var start, tick = 0;
var f = function() {
    if (!start) start = new Date().getTime();
    var now = new Date().getTime();
    if (now < start + tick*1000) {
        setTimeout(f, 0);
    } else {
        tick++;
        var diff = now - start;
        var drift = diff % 1000;
        $('<li>').text(drift + "ms").appendTo('#results');
        setTimeout(f, 990);
    }
};

setTimeout(f, 990);

Run demo

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.