Как браузеры определяют, какое время 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;
    }
}

здесь 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 ближе к тому, что я прошу?

EDIT: спецификация HTML5 говорит, что браузеры не должны разрешить setInterval работать с интервалом менее 4 мс.

5 ответов


Я думаю, во-первых, мы должны спросить себя, что мы ожидаем от интервальных функций:

  • они должны поддерживать контекст: интервал, в котором вы не могли бы увеличить счетчик надежно, был бы довольно катастрофическим

  • они должны выполнять все, что находится в функции, которая имеет приоритет над интервалом выполнения (то же самое здесь, что таймер должен подняться, прежде чем мы снова увеличим)

  • он должен быть отделите стек выполнения от остальной части нашего кода js (мы не хотим ждать, пока эти таймеры закончат свой бизнес, пока остальные не начнут выполняться);

  • они должны знать о контексте, в котором они находятся, независимо от того, насколько он огромен (мы хотим иметь возможность использовать jQuery внутри наших функций таймера, например).

так как Мэтт Грир сказал выше, интервалы по дизайну не точны, и это в основном потому, что мы на самом деле не ожидайте, что они будут точными, но будут надежно выполнять код в данный момент времени.

если вы посмотрите на реализацию Chromium, вы увидите, что реализация setTimeout и setInterval основана на DOMTimer::установить, который получает переданный контекст выполнения, действие и если таймер является одним выстрелом

это передается RunloopTimer, который выполняет цикл с помощью системных таймеров (как вы видите здесь)

(кстати, chromium устанавливает минимум 10 мс для интервала, как вы можете видеть здесь)

каждое выполнение действия обрабатывается здесь, который не делает никакого утверждения за время, прошедшее с момента последнего выполнения, превышающего или под определенным лимитом времени.

напротив, он только утверждает, что уровень вложенности таймера не слишком глубокий, замедляя таймеры, которые используют слишком много ресурсов / работает слишком медленно для данного интервала с этого:

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

augmentRepeatInterval просто добавляет больше миллисекунд к интервалу:

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

Итак, что мы можем заключить?

  • измерять точность времени интервалов или таймаутов пустая трата времени. То, что вы должны и можете заботиться о том, что вы не устанавливаете слишком низкие интервалы для вещей, которые вы хотите выполнить в функции. Броузер сделает все возможное, чтобы выполнить ваши интервалы и таймауты своевременно, но это не гарантирует точное время.

  • выполнение интервала зависит от среды, браузера, реализации, версии, контекста, самого действия и так далее и так далее. Это не должно быть точным, и если вы хотите запрограммировать что-то точное с помощью setTimeout или setInterval, вы либо сошли с ума сейчас, либо сойдете с ума позже.

  • Вы сказали что в вашем коде, когда вы добавили тяжелое выполнение функций, таймер стал более точным. Это может быть по разным причинам (возможно, он получает больше памяти, более эксклюзивное время процессора, больше рабочих и так далее). Меня это очень интересует, но вы еще не предоставили код, который сделал тяжелое выполнение. Поэтому, если вы хотите получить ответы на этот вопрос, пожалуйста, предоставьте код, потому что без него трудно что-либо предположить.

  • но что бы это ни было, что делает ваш интервалы работают более своевременно, это не является надежным в любом случае. Вы, скорее всего, получите всевозможные результаты, если начнете измерять в разных системах.


обновление

кроме этого, код, который ни к чему не приводит, может быть также оптимизирован (не выполнен, выполнен только один раз, выполнен лучшим образом) браузером JS engine. Позвольте мне привести пример, основанный на вашем (все вещи, выполненные в 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' }
}

вы обнаружите, что первое исполнение после того, как вы нажмете start займет века, в то время как дальнейшие исполнения не (по крайней мере, в webkit). Это потому, что код на итерации ничего не меняет, и браузер распознает это после первого выполнения и просто больше не выполняет его.

давайте посмотрим, как он изменяется, если выполнение должно поддерживать привязку к внешней переменной (kв этом case):

var k;

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

ок, здесь мы имеем некоторое влияние на время выполнения, но это все еще довольно быстро. Браузер знает, что цикл всегда будет делать то же самое, но он выполняет его один раз. Ну и что, если итерация действительно создает огромную строку, например?

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

эта конкатенация массива не может оптимизироваться и будет душить практически любой браузер медленно и болезненно.

таким образом, в вашем случае тяжелое выполнение может привести к резервированию и мгновенному освобождению оптимизатором большего объема памяти. Может быть, глоток свежего воздуха, дополнительная память и бездействующий процессор заставили вашу функцию прыгать от радости, но, как я уже сказал, Нет ничего надежного, не посмотрев на ваш тяжелый код выполнения.


setInterval и setTimeout не точны, и не предназначены для этого. JavaScript однопоточен, поэтому setTimeout/setInterval в основном говорит: "Возьмите этот кусок кода и вставьте его в очередь выполнения. Когда вы доберетесь до него, если прошло достаточно времени, то выполните его, иначе вставьте его обратно в очередь и повторите попытку позже".

Если у вас есть setInterval набор для 4 миллисекунд, но вещи в вашем приложении занимают 10 мс для запуска, то нет никакого способа setInterval может когда-либо работать на 4 миллисекунды, в самое лучшее оно вытянет интервалы 10ms.

Если это для целей анимации / игры, то дайте requestAnimationFrame попробовать. Я понятия не имею, как это реализовано, но одна вещь, которую он обещает, - это более точное время.


Я могу придумать ряд причин, по которым браузер может замедлить ваши интервалы через некоторое время (это все догадки, но это то, что вы просили, я думаю):

  1. другие вещи должны быть запланированы, как вывоз мусора, и т. д... на это нужно время.
  2. браузер решает, что ваш интервальный таймер занимает слишком много процессора или сжигает слишком много батареи, поэтому через некоторое время он дросселирует вас. Некоторые браузеры документированы для этого, когда вкладка теряет фокус, поэтому Я мог представить, что они могли бы сделать это и в другое время.
  3. браузер первоначально откладывает другую работу, которую нужно сделать в пользу вашего интервала, но через некоторое время он больше не откладывает эту работу, и это занимает некоторое время.

вопрос: 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 как частоту кадров и продолжается до тех пор, пока операционная система не решит ограничить выделение ЦП или памяти процессом браузера. Браузер по-прежнему просить обработать это с минимальным интервалом (4 мс в случае firefox), но операционная система может действительно заботиться меньше. После 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);
        }
}

вы должны физически услышать ваш процессор плачет при запуске, если у вас нет действительно удивительный компьютер (который имеет абсолютно ничего делать с браузером).


во-первых, если вы делаете графику (что говорит о кадрах), вы, вероятно, должны использовать requestAnimationFrame вместо setInterval.

Я запустил ваш код в chrome 20, и я не видел частоты кадров ниже 244 в первые несколько минут. Ты видишь что-то другое? Большинство частот кадров приходили на 249 или 250, что является максимальным допустимым спецификацией при использовании setInterval, даже после запуска в то время как.

в течение длительного периода времени я в среднем 247 кадров в секунду, потеря, которая может быть легко связана с уборкой мусора, происходящей в неподходящее время.