While working on a timing sensitive project, I used the code below to test the granularity of timing events available, first on my desktop machine in Firefox, then as node.js code on my Linux server. The Firefox run produced predictable results, averaging 200 fps on a 1ms timeout and indicating I had timing events with 5ms granularity.
Now I know that if I used a timeout value of 0, the Chrome V8 engine Node.js is built on would not actually delegate the timeout to an event but process it immediately. As expected, the numbers averaged 60,000 fps, clearly processing constantly at CPU capacity (and verified with top). But with a 1ms timeout the numbers were still around 3.5-4 thousand cycle()'s per second, meaning Node.js cannot possibly be respecting the 1ms timeout which would create a theoretical maximum of 1 thousand cycle()'s per second.
Playing with a range of numbers, I get:
The behavior of setTimeout(func, 0) seems excusable, because the ECMAScript specification presumably makes no promise of setTimout delegating the call to an actual OS-level interrupt. But the result for anything 0 < x <= 1.0 is clearly ridiculous. I gave an explicit amount of time to delay, and the theoretical minimum time for n calls on x delay should be (n-1)*x. What the heck is V8/Node.js doing?
var timer, counter = 0, time = new Date().getTime();
function cycle() {
counter++;
var curT = new Date().getTime();
if(curT - time > 1000) {
console.log(counter+" fps");
time += 1000;
counter = 0;
}
timer = setTimeout(cycle, 1);
}
function stop() {
clearTimeout(timer);
}
setTimeout(stop, 10000);
cycle();
This is due to when a function is executed as a parameter to setTimeout , the execution context is different to the execution context of the function! Now this will print out undefined because the this keyword is executed in the context of the setTimeout function and is therefore not defined.
setTimeout() can be used to schedule code execution after a designated amount of milliseconds. This function is similar to window. setTimeout() from the browser JavaScript API, however a string of code cannot be passed to be executed.
This is because even though setTimeout was called with a delay of zero, it's placed on a queue and scheduled to run at the next opportunity; not immediately. Currently-executing code must complete before functions on the queue are executed, thus the resulting execution order may not be as expected.
setTimeout(fn, 0) !== 0ms. When working with timers, setting a delay of 0, does not equate to 0ms or “instant” execution. This longer than expected delay may occur because the OS/browser/system is busy with other tasks or because the timer has been throttled.
From the node.js api docs for setTimeout(cb, ms)
(emphasis mine):
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.
I suppose that "as close as possible" means something different to the implementation team than to you.
[Edit] Incidentally, it appears that the setTimeout()
function isn't mandated by any specification (although apparently part of the HTML5 draft). Moreover, there appears to be a 4-10ms de-facto minimum level of granularity, so this appears to be "just how it is".
The great thing about open source software is that you can contribute a patch to include a higher resolution per your needs!
For completeness I would like to point out to the nodeJS implementation:
https://github.com/nodejs/node-v0.x-archive/blob/master/lib/timers.js#L214
Which is:
// Timeout values > TIMEOUT_MAX are set to 1.
var TIMEOUT_MAX = 2147483647; // 2^31-1
...
exports.setTimeout = function(callback, after) {
var timer;
after *= 1; // coalesce to number or NaN
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
after = 1; // schedule on next tick, follows browser behaviour
}
timer = new Timeout(after);
...
}
Remember this statement:
IDLE TIMEOUTS
Because often many sockets will have the same idle timeout we will not use one timeout watcher per item. It is too much overhead.
Instead we'll use a single watcher for all sockets with the same timeout value and a linked list.This technique is described in the libev manual: http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts
And we pass the same timeout value (1)
here.
The implementation for Timer
is here:
https://github.com/nodejs/node-v0.x-archive/blob/master/src/timer_wrap.cc
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With