Alex Rivera | Logout

node.js: setInterval() skipping calls

Asked 2012-09-14T23:39:30.923
11

For an upcoming project with node.js I need to perform various housekeeping tasks at periodic times. Specifically some tasks every millisecond, others every 20 ms (50 times per second) and still others every second. So I thought about using setInterval(), with funny results: many function calls were being skipped.

The benchmark I used is as follows:

var counter = 0;
var seconds = 0;
var short = 1;
setInterval(function() {
        counter ++;
    }, short);
setInterval(function() {
        seconds ++;
        log('Seconds: ' + seconds + ', counter: ' +
             counter + ', missed ' +
             (seconds * 1000 / short - counter));
    }, 1000);

There is a long timer of one second and a short one that can be adjusted using the variable short, in this case 1 ms. Every second we print the difference between the number of expected ticks in the short cycle and the actual number of times the short counter was updated.

Here is how it behaves when the short timer is 1 ms:

2012-09-14T23:03:32.780Z Seconds: 1, counter: 869, missed 131
2012-09-14T23:03:33.780Z Seconds: 2, counter: 1803, missed 197
2012-09-14T23:03:34.781Z Seconds: 3, counter: 2736, missed 264
...
2012-09-14T23:03:41.783Z Seconds: 10, counter: 9267, missed 733

Many function calls are skipped. Here it is for 10 ms:

2012-09-14T23:01:56.363Z Seconds: 1, counter: 93, missed 7
2012-09-14T23:01:57.363Z Seconds: 2, counter: 192, missed 8
2012-09-14T23:01:58.364Z Seconds: 3, counter: 291, missed 9
...
2012-09-14T23:02:05.364Z Seconds: 10, counter: 986, missed 14

Better, but roughly one function call is skipped every second. And for 20 ms:

2012-09-14T23:07:18.713Z Seconds: 1, counter: 46, missed 4
2012-09-14T23:07:19.713Z Seconds: 2, counter: 96, missed 4
2012-09-14T23:07:20.712Z Seconds: 3, counter: 146, missed 4
...
2012-09-14T23:07:27.714Z Seconds: 10, counter: 49
Edit
Report

1 Answer

2

The answer happens to be a combination of those given by Vadim and zer02, so I am leaving a write-up here. As Vadim said, the system cannot cope with too frequent updates, and adding some load to the system is not going to help. Or rather the runtime cannot cope; the system should be more than capable of firing the callback every millisecond if needed, but for some unexplained reason often it doesn't want to.

The solution is to use accurate timers, as zer02 commented. Do not be misled by the name; the mechanism used is the same setTimeout(), but the delay is adjusted depending on the time left until the timer should fire. So, if the time is over then the "accurate timer" will call setTimeout(callback, 0) which is run immediately. System load is, surprisingly, less than with setInterval(): about 2% of the CPU instead of 5%, in my very unscientific sample.

This simple function may come in handy:

/**
 * A high resolution timer.
 */
function timer(delay, callback)
{
    // self-reference
    var self = this;

    // attributes
    var counter = 0;
    self.running = true;
    var start = new Date().getTime();

    /**
     * Delayed running of the callback.
     */
    function delayed()
    {
        callback(delay);
        counter ++;
        var diff = (new Date().getTime() - start) - counter * delay;
        if (!self.running) return;
        setTimeout(delayed, delay - diff);
    }

    // start timer
    delayed();
    setTimeout(delayed, delay);
}

To use, just call new timer(delay, callback);. (Yes, I reversed the order of the parameters since having the callback first is very annoying.) To stop it, set timer.running = false.

One final note: setTimeout(callback, delay) does not use recursion as I feared (as in: wait for some time, then invoke the callback), it just places t

answered 2012-09-17T21:29:57.997

Your Answer