It would seem that in general, browsers will in certain cases modify, even beyond a minimum clamp, the actual time interval that setInterval
uses. For instance, I have the following code:
function start() {
window.setInterval(function() {
update();
}, 1);
}
lastTime = new Date;
numFrames = 0;
lastFrames = 0;
function update() {
numFrames++;
if (new Date - lastTime >= 1000) {
lastFrames = numFrames;
numFrames = 0;
lastTime = new Date;
}
}
Here, lastFrames
will give us the number of frames over what is approximately the past second. When used in Chrome, Firefox, and Safari, this code doesn't run at one millisecond. Of course, each browser has an arbitrary minimum time between setInterval
calls, so this is to be expected. However, as the page continues to run, the frame rate will continue to decrease, even if the tab is still focused. The only way I've found to fix this is to make the browser do something. Something along these lines seems to make the browser run setInterval
as fast as it can:
function start() {
window.setInterval(function() {
update();
}, 1);
}
lastTime = new Date;
numFrames = 0;
lastFrames = 0;
function update() {
numFrames++;
if (new Date - lastTime >= 1000) {
lastFrames = numFrames;
numFrames = 0;
lastTime = new Date;
}
//doIntensiveLoop, processing, etc.
}
Thus, my question is this: What is the browser looking for to justify running setInterval
closer to what I ask it to?
EDIT: The HTML5 spec says that browsers should not allow setInterval to run at an interval lower than 4ms.
The browser has at least three threads: Javascript engine thread, UI thread, and timing thread, where the timing of setTimeout and setInterval are done by the timing thread. When calling setTimeout or setInterval, a timer thread in the browser starts counting down and when time up puts the callback function in javascript thread's execution stack.
Definition and Usage. The setInterval() method calls a function or evaluates an expression at specified intervals (in milliseconds). The setInterval() method will continue calling the function until clearInterval() is called, or the window is closed. The ID value returned by setInterval() is used as the parameter for the clearInterval()...
So JavaScript does not wait for the function (callback function) passed to the setTimeout or setInterval method to finish executing before it moves on to the next task or line of code. In the example above, if we had written it the second way, the timer will not stop running.
The HTML5 standard says: “after five nested timers, the interval is forced to be at least 4 milliseconds.”. Let’s demonstrate what it means with the example below. The setTimeout call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the times array.
setInterval
and setTimeout
are not accurate at all, and were not designed to be. JavaScript is single threaded, so setTimeout/setInterval
basically says "take this chunk of code and stick it in the run queue. When you get to it, if enough time has passed, then execute it, else stick it back in the queue and try again later".
If you have a setInterval set for 4 milliseconds, but things in your app take 10ms to run, then there is no way setInterval can ever run at 4 milliseconds, at the very best it will pull off 10ms intervals.
If this is for animation/game purposes, then give requestAnimationFrame a try. I have no idea how it's implemented, but one thing it does promise is more accurate timing.
I think first, we have to ask ourselves what we expect from Interval functions:
They have to maintain the context: an interval in which you could not increment a counter reliably would be quite disastrous
They should execute whatever is in the function, which has priority over the execution interval (same here, that timer has to go up before we increment again)
It should have a separate execution stack from the rest of our js code (we don't want to wait for those timers to finish their business until the rest starts executing);
They should be aware of the context they're in, no matter how vast it is (we want to be able to use jQuery inside our timer functions, for example).
So, as Matt Greer said above, Intervals are by design not exact, and that's mainly because we do not really expect them to be exact, but to be reliably executing code at a given time.
If you have a look at the Chromium implementation, you will see that the Implementation of setTimeout and setInterval is based on the DOMTimer::install, which gets passed the execution context, the action and if the timer is a single shot
This gets passed to the RunloopTimer, which executes the loop with the help of system timers (as you see here)
(by the way, chromium installs a minimum of 10ms for the interval, as you can see here)
Every Execution of the action is handled here, which does no assertion whatsoever over the time passed since the last execution being over or under a certain timing limit.
In contrary, it only asserts that the Timer nesting level does not get too deep by slowing down timers that are using too much resources / running too slowly for the given interval with this:
if (m_nestingLevel >= maxTimerNestingLevel)
augmentRepeatInterval(minimumInterval - repeatInterval());
}
augmentRepeatInterval simply adds more milliseconds to the interval:
void augmentRepeatInterval(double delta) { augmentFireInterval(delta); m_repeatInterval += delta; }
So, what can we conclude?
Measuring the time accuracy of Intervals or Timeouts is a waste of time. The thing you should and can care about is that you don't set the intervals too low for the things you want to execute in the function. The browser will do the best it can to execute your intervals and timeouts in a timely fashion, but it won't guarantee exact timing.
The Interval execution depends on the environment, the browser, the implementation, the version, the context, the action itself and so on and so on. It is not meant to be exact, and if you want to program something exact with setTimeout or setInterval, you're either crazy now or will go crazy later.
You said that in your code, when you added heavy execution to the functions, the timer got more accurate. This can be for different reasons (maybe it gets more memory, more exclusive cpu time, more workers and so on). I'm very interested in that, but you did not provide the code that did the heavy execution yet. So if you want some answers on that, please provide the code, because it's difficult to assume anything without it.
But whatever it is that makes your intervals run more timely, it's not reliable in any way. You will most likely get all kinds of various results if you start measuring on different systems.
Other than that, code that does not lead to anything may be as well optimized (not executed, executed only once, executed in a better way) by the browser js engine. Let me give an example based on yours (all things executed in chromium):
function start() {
window.setInterval(function() {
update();
}, 1);
}
lastTime = new Date;
numFrames = 0;
lastFrames = 0;
function update() {
console.log(new Date() - lastTime);
lastTime = new Date();
for (var i=0; i < 1000000; i++) { var k = 'string' + 'string' + 'string' }
}
You will find that the first execution after you hit start
will take ages, whereas the further executions don't (at least in webkit). That's because the code in the iteration does not change anything, and the browser recognizes that after the first execution and simply does not execute it anymore.
Let's look how it changes if the execution has to maintain a binding to an outer variable (k
in this case):
var k;
function update() {
console.log(new Date() - lastTime);
lastTime = new Date();
for (var i=0; i < 1000000; i++) { k = 'string' + 'string' + 'string' }
}
Ok, here we have some impact on the execution time, but it is still quite fast. The browser knows that the for loop will always do the same thing, but it executes it once. So what if the iteration really does create a huge string, for example?
var k;
function update() {
console.log(new Date() - lastTime);
lastTime = new Date();
k = '';
for (var i=0; i < 1000000; i++) { k += i.toString() }
}
This puts the browser in a world of hurt, because it has to return this millions-of-characters string. Can we make that more painful?
var k;
function update() {
console.log(new Date() - lastTime);
lastTime = new Date();
k = '';
for (var i=0; i < 1000000; i++) { k = ['hey', 'hey', 'hey'].join('') }
}
This array concatenation cannot be optimized and will choke almost any browser slowly and painfully.
So, in your case, the heavy execution may have lead for more memory to be reserved and instantly freed by the optimizer. May be that breath of fresh air and extra memory and idle cpu made your function jump with joy, but as I said, there's nothing reliable there without having looked at your heavy execution code.
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