通常情况下,浏览器在某些情况下会修改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毫秒的间隔运行。
答案 0 :(得分:5)
setInterval
和setTimeout
根本不准确,并非设计为。 JavaScript是单线程的,所以setTimeout/setInterval
基本上说“把这块代码放在运行队列中。当你到达它时,如果已经过了足够的时间,那么执行它,否则将它粘在队列中然后再试一次“。
如果你将setInterval设置为4毫秒,但你的应用程序中的东西需要10毫秒才能运行,那么setInterval就无法在4毫秒内运行,最好它会以10毫秒的间隔运行。
如果这是用于动画/游戏目的,请尝试requestAnimationFrame。我不知道它是如何实现的,但它承诺的一件事就是更精确的计时。
答案 1 :(得分:3)
我认为首先,我们必须问自己我们对Interval函数的期望:
他们必须保持上下文:一个你无法可靠地增加一个计数器的间隔将是非常灾难性的
他们应该执行函数中的任何内容,该函数优先于执行间隔(此处相同,定时器必须在我们再次递增之前上升)
它应该与我们的其余js代码有一个单独的执行堆栈(我们不想等待那些定时器完成他们的业务,直到其余的开始执行);
他们应该知道他们所处的环境,无论它有多大(我们希望能够在我们的计时器功能中使用jQuery)。
所以,正如Matt Greer所述,Intervals在设计上并不精确,这主要是因为我们并不真正期望它们是精确的,而是在给定时间可靠地执行代码。
如果您查看Chromium实现,您会看到setTimeout和setInterval的实现基于DOMTimer::install,它传递执行上下文,操作以及计时器是否为单击
这会传递给RunloopTimer,它在系统计时器的帮助下执行循环(as you see here)
(顺便说一句,在这段时间内,铬安装至少10毫秒,你可以看到here)
每次执行该操作都是handled here,在上次执行超过或超过某个时间限制的过程中,该操作没有任何断言。
相反,它只通过减慢使用过多资源/在给定时间间隔内运行速度太慢的定时器来断言定时器嵌套级别does not get too deep:
if (m_nestingLevel >= maxTimerNestingLevel)
augmentRepeatInterval(minimumInterval - repeatInterval());
}
augmentRepeatInterval只是为间隔添加更多毫秒:
void augmentRepeatInterval(double delta) { augmentFireInterval(delta); m_repeatInterval += delta; }
那么,我们可以得出什么结论呢?
测量间隔或超时的时间准确度浪费时间。你应该和可以关心的事情是你没有为你想在函数中执行的东西设置太低的间隔。浏览器将尽最大努力及时执行间隔和超时,但不能保证准确的时间安排。
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让你的功能快乐地跳起来,但正如我所说,没有看到你繁重的执行代码就没有可靠的东西。
答案 2 :(得分:1)
我可以想到一些原因,浏览器可能会在一段时间后减慢您的间隔时间(这是所有猜想,但这就是您要求的,我认为):
答案 3 :(得分: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的情况下为4ms)处理这个问题,但操作系统可能真的不在乎。运行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哭声。
答案 4 :(得分:0)
首先,如果你正在做图形(关于框架的说法表明你是),你可能应该使用requestAnimationFrame而不是setInterval。
我在chrome 20中运行了你的代码,我在前几分钟看不到帧率低于244。你看到了不同的东西吗?大多数帧速率都是249或250,这是使用setInterval时规范允许的最大值,即使在运行一段时间后也是如此。
在很长一段时间里,我平均每秒247帧,这可能很容易造成垃圾收集在不合时宜的时候发生。