问题
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: 495, missed 5
Finally for 100 ms:
2012-09-14T23:04:25.804Z Seconds: 1, counter: 9, missed 1
2012-09-14T23:04:26.803Z Seconds: 2, counter: 19, missed 1
2012-09-14T23:04:27.804Z Seconds: 3, counter: 29, missed 1
...
2012-09-14T23:04:34.805Z Seconds: 10, counter: 99, missed 1
In this case it skips very few calls (the gap increased to 2 after 33 seconds and to 3 after 108 seconds.
The numbers vary, but are surprisingly consistent between runs: Running the first 1 ms benchmark three times yielded a delay after 10 seconds of 9267, 9259 and 9253.
I have found no references for this particular problem. There is this much cited Ressig post and lots of related JavaScript questions, but most assume that the code runs in a browser and not in node.js.
Now for the dreaded question: what is going on here? Just joking; obviously function calls are being skipped. But I fail to see the pattern. I thought that the long cycles might be preventing the short ones, but it doesn't make any sense in the 1 ms case. Short cycle function calls are not overlapping since they just update a variable, and the node.js process is near 5% CPU even with a short cycle of 1 ms. Load average is high though, at about 0.50. I don't know why a thousand calls are stressing my system so much, though, since node.js handles many more clients perfectly; it must be true that setInterval() is CPU intensive (or I am doing something wrong).
An obvious solution is to group function calls using longer timers, and then run short cycle function calls many times to simulate a shorter timer. Then use the long cycle as a "broom wagon" that makes any calls missed in the lower intervals. An example: set up 20 ms and 1000 ms setInterval() calls. For 1 ms calls: call them 20 times in the 20 ms callback. For the 1000 ms call: check how many times the 20ms function has been called (e.g. 47), do any remaining calls (e.g. 3). But this scheme is going to be a bit complex, since calls may overlap in interesting ways; also it will not be regular although it may look like it.
The real question is: can it be done better, either with setInterval() or other timers within node.js? Thanks in advance.
回答1:
SetInterval functions in javascript are not accurate. You should try to use a high resolution timer.Building accurate Timers in javascript
回答2:
Look at this doc: http://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg
It is important to note that your callback will probably not be called in exactly delay milliseconds - Node.js makes no guarantees about the exact timing of when the callback will fire, nor of the ordering things will fire in. The callback will be called as close as possible to the time specified.
This happens because application code blocks the event loop. All timers and I/O events can be handled only on the nextTick
.
You can see this behaviour with this code:
setInterval(function() {
console.log(Date.now());
for (var i = 0; i < 100000000; i++) {
}
}, 1);
Try to change iterations count and see results.
Ideally, the timer will be triggered exactly if the applications tick will last less than one ms. But this is not practicable in a real application.
回答3:
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 the callback in a queue which will be called by the runtime when its turn comes, in the global context.
回答4:
I Disabled Debugger and tried again.It worked fine for me
来源:https://stackoverflow.com/questions/12433296/node-js-setinterval-skipping-calls