How do browsers determine what time setInterval sh

2020-03-19 21:16发布

问题:

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.

回答1:

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.


UPDATE

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 (kin 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.



回答2:

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.



回答3:

I can think of a number of reasons that a browser might slow down your intervals after awhile (this is all conjecture, but that's what you asked for, I think):

  1. Other things need to be scheduled to run like garbage collection, etc... that take time.
  2. The browser decides that your interval timer is taking too much CPU or burning too much battery so it throttles you after awhile. Some browsers are documented to do this when a tab loses focus so I could imagine they might do it at other times too.
  3. The browser initially defers other work that needs doing in favor of your interval, but after awhile, it no longer defers that work and it takes some time.


回答4:

Question: What is the browser looking for to justify running setInterval closer to what I ask it to?

Answer: Nothing. It does it as fast as possible. If it went too quick, it waits. "As possible" will depend on the environment.

Issue: However, as the page continues to run, the frame rate will continue to decrease, even if the tab is still focused.

This is an operating system issue.

When your code first runs on page load it reports 250 as the framerate, and continues until the operating system decides to limit CPU or memory allocation to the browser's process. The browser is still asking to process this at the min interval possible(4ms in firefox's case), but the operating system could really care less. After 30 minutes of runtime, it is still possible to gain the operating system's attention and return to the full 250 framerate.

Just to re-iterate, this has nothing to do with the browser.

Try putting this in as your "intensive loop" and then try to explain how the result is due to the browser:

for (var intense = 0; intense < 10000; intense++) {
        var dec = intense * (5039 + intense);
        for (var processing = 0; processing < intense; processing++) {
            var float = dec / (processing + 1);
        }
}

You should physically hear your cpu crying when running that, unless you have a truly amazing computer (which has absolutely nothing to do with a browser).



回答5:

Firstly, If you are doing graphics (which talk of frames suggests you are), you should probably be using requestAnimationFrame rather than setInterval.

I ran your code in chrome 20 and I did not see frame rates lower than 244 in the first few minutes. Are you seeing something different to this? Most frame rates were coming in at 249 or 250 which is the maximum allowed by the spec when using setInterval, even after running for a while.

Over long periods of time I was averaging 247 frames per second, a wastage that could easily be attributable to garbage collection occuring at inopportune times.