6

通常,浏览器似乎会在某些情况下修改setInterval使用的实际时间间隔,甚至超出最小限制。例如,我有以下代码:

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;
    }
}

在这里,lastFrames将为我们提供大约在过去一秒内的帧数。在 Chrome、Firefox 和 Safari 中使用时,此代码不会在一毫秒内运行。当然,每个浏览器在setInterval调用之间都有一个任意的最小时间,所以这是可以预料的。但是,随着页面继续运行,即使选项卡仍处于焦点状态,帧速率也会继续降低。我发现解决这个问题的唯一方法是让浏览器做一些事情。这些方面的东西似乎使浏览器setInterval尽可能快地运行:

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.
}

因此,我的问题是:浏览器在寻找什么来证明运行setInterval更接近我的要求?

编辑:HTML5 规范说浏览器不应允许 setInterval 以低于 4 毫秒的间隔运行。

4

5 回答 5

5

setInterval并且setTimeout根本不准确,也不是设计的。JavaScript 是单线程的,所以setTimeout/setInterval基本上说“把这段代码放在运行队列中。当你到达它时,如果已经过了足够的时间,然后执行它,否则把它放回队列中,稍后再试” .

如果你有一个 setInterval 设置为 4 毫秒,但你的应用程序中的东西需要 10 毫秒才能运行,那么 setInterval 不可能以 4 毫秒运行,最好它会拉出 10 毫秒的间隔。

如果这是出于动画/游戏目的,请尝试requestAnimationFrame 。我不知道它是如何实现的,但它确实承诺的一件事是更准确的时间安排。

于 2012-07-06T23:04:00.107 回答
3

我认为首先,我们必须问自己对 Interval 函数的期望:

  • 他们必须维护上下文:您无法可靠地增加计数器的时间间隔将是非常灾难性的

  • 他们应该执行函数中的任何内容,其优先级高于执行间隔(此处相同,该计时器必须在我们再次递增之前上升)

  • 它应该与我们的其余 js 代码有一个单独的执行堆栈(我们不想等待那些计时器完成他们的业务,直到其余的开始执行);

  • 他们应该知道他们所处的上下文,无论它有多大(例如,我们希望能够在我们的计时器函数中使用 jQuery)。

所以,正如马特·格里尔(Matt Greer)上面所说,间隔在设计上并不精确,这主要是因为我们并不真正期望它们是精确的,而是在给定时间可靠地执行代码。

如果您查看 Chromium 实现,您会看到 setTimeout 和 setInterval 的实现基于DOMTimer::install,它会传递执行上下文、动作以及计时器是否为单次

这将传递给 RunloopTimer,后者在系统计时器的帮助下执行循环(如您在此处看到的)

(顺便说一下,Chromium 至少安装了 10ms 的间隔,如您在此处看到的)

动作的每一次执行都在这里处理,自上次执行超过或低于某个时间限制以来,它在过去的时间里不做任何断言。

相反,它仅通过减慢使用过多资源/在给定时间间隔内运行速度过慢的计时器来断言计时器嵌套级别不会变得太深,如下所示:

if (m_nestingLevel >= maxTimerNestingLevel)
            augmentRepeatInterval(minimumInterval - repeatInterval());
    }

augmentRepeatInterval 只是将更多毫秒添加到间隔:

void augmentRepeatInterval(double delta) { augmentFireInterval(delta); m_repeatInterval += delta; }

那么,我们能得出什么结论呢?

  • 测量 Intervals 或 Timeouts 的时间准确性是浪费时间。您应该并且可以关心的是,对于要在函数中执行的事情,不要将间隔设置得太低。浏览器将尽其所能及时执行您的时间间隔和超时,但它不能保证准确的时间。

  • Interval 执行取决于环境、浏览器、实现、版本、上下文、操作本身等等。这并不意味着精确,如果你想用 setTimeout 或 setInterval 编写一些精确的东西,你要么现在疯了,要么以后会疯。

  • 您说在您的代码中,当您向函数添加大量执行时,计时器变得更加准确。这可能是出于不同的原因(也许它获得了更多的内存、更多的独占 CPU 时间、更多的工人等等)。我对此非常感兴趣,但是您还没有提供执行繁重执行的代码。因此,如果您想要一些答案,请提供代码,因为没有它很难假设任何事情。

  • 但无论是什么让你的间歇跑得更及时,它在任何方面都不可靠。如果您开始在不同的系统上进行测量,您很可能会得到各种不同的结果。


更新

除此之外,不会导致任何事情的代码可能会被浏览器 js 引擎优化(不执行,只执行一次,以更好的方式执行)。让我举一个基于你的例子(所有东西都用铬执行):

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' }
}

您会发现点击后的第一次执行start需要很长时间,而进一步的执行则不需要(至少在 webkit 中)。那是因为迭代中的代码没有改变任何东西,并且浏览器在第一次执行后识别出它并且不再执行它。

让我们看看如果执行必须保持与外部变量的绑定(k在本例中),它会如何变化:

var k;

function update() {
  console.log(new Date() - lastTime);
  lastTime = new Date();
  for (var i=0; i < 1000000; i++) { k = 'string' + 'string' + 'string' }
}

好的,这里我们对执行时间有一些影响,但还是相当快的。浏览器知道 for 循环总是做同样的事情,但它只执行一次。那么,例如,如果迭代确实创建了一个巨大的字符串呢?

var k;

function update() {
  console.log(new Date() - lastTime);
  lastTime = new Date();
  k = '';
  for (var i=0; i < 1000000; i++) { k += i.toString() }
}

这使浏览器陷入了痛苦的境地,因为它必须返回这个数百万个字符的字符串。我们能让这更痛苦吗?

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('') }
}

这种数组连接无法优化,并且会缓慢而痛苦地阻塞几乎所有浏览器。

因此,在您的情况下,繁重的执行可能会导致更多内存被保留并由优化器立即释放。可能是新鲜空气、额外内存和空闲 cpu 让你的函数高兴地跳跃,但正如我所说,如果没有查看你繁重的执行代码,那里没有什么可靠的。

于 2012-07-10T18:30:55.383 回答
1

我可以想到浏览器可能会在一段时间后减慢您的时间间隔的一些原因(这都是猜测,但我认为这就是您所要求的):

  1. 需要安排其他事情来运行,例如垃圾收集等……这需要时间。
  2. 浏览器认为您的间隔计时器占用了过多的 CPU 或消耗了过多的电池,因此它会在一段时间后限制您。有些浏览器被记录为当标签失去焦点时执行此操作,所以我可以想象他们也可能在其他时间进行。
  3. 浏览器最初会推迟需要执行的其他工作以支持您的时间间隔,但过了一段时间,它不再推迟该工作并且需要一些时间。
于 2012-07-07T00:29:04.220 回答
0

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

答:没什么。它会尽可能快地完成。如果它走得太快,它会等待。“尽可能”将取决于环境。

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

这是一个操作系统问题。

当您的代码第一次在页面加载时运行时,它会报告 250 作为帧速率,并一直持续到操作系统决定限制 CPU 或内存分配给浏览器进程。浏览器仍然要求以可能的最小时间间隔(在 Firefox 的情况下为 4 毫秒)处理此问题,但操作系统可能真的不在乎。运行 30 分钟后,仍然有可能获得操作系统的注意并恢复到满 250 帧速率。

只是重申一下,这与浏览器无关。

尝试将其作为您的“密集循环”,然后尝试解释结果是如何归因于浏览器的:

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

运行它时你应该能听到你的 cpu 哭泣,除非你有一台非常棒的计算机(这与浏览器完全无关)。

于 2012-07-10T23:06:28.540 回答
0

首先,如果你正在做图形(帧的谈话暗示你是),你可能应该使用requestAnimationFrame而不是 setInterval。

我在 chrome 20 中运行了您的代码,但在最初的几分钟内我没有看到低于 244 的帧速率。你看到与此不同的东西吗?大多数帧速率为 249 或 250,这是使用 setInterval 时规范允许的最大值,即使在运行一段时间后也是如此。

在很长一段时间内,我平均每秒处理 247 帧,这种浪费很容易归因于不合时宜的垃圾收集。

于 2012-07-12T21:03:54.220 回答