Микросекундный тайминг в JavaScript

100

Есть ли в JavaScript какие-либо функции синхронизации с разрешением в микросекунды?

Мне известно о timer.js для Chrome, и я надеюсь, что будет решение для других дружественных браузеров, таких как Firefox, Safari, Opera, Epiphany, Konqueror и т. Д. Я не заинтересован в поддержке какого-либо IE, но ответы, включая IE приветствуются.

(Учитывая низкую точность миллисекундного времени в JS, я не затаил дыхание на этом!)

Обновление: timer.js рекламирует разрешение в микросекундах, но просто умножает показание миллисекунды на 1000. Проверено тестированием и проверкой кода. Разочарованный. : [

mwcz
источник
2
Что вы пытаетесь сделать в браузере, требующем точности до микросекунд? В общем, гарантии производительности поведения браузеров не так точны.
Юлий
4
Не произойдет. Вы вообще не можете доверять точности до микросекунд, даже если бы она существовала. Единственный надежный вариант использования, который я могу себе представить, - это собственные клиенты в Chrome, но тогда вам наплевать на JS API. Также мне нравится относиться к "Epiphany" как к первоклассному браузеру и игнорировать IE.
Raynos 04
6
«Получение» времени в javascript занимает некоторое время, как и его возврат, и задержка увеличивается, если вы находитесь на веб-странице, которая перерисовывает или обрабатывает события. Я бы даже не стал рассчитывать на точность в ближайшие 10 миллисекунд.
kennebec 04
1
Например, всплывающие окна на сверхвысокой скорости? По сути, проблема заключается в том, что предоставление внешним сторонам слишком большого доступа к пользовательским машинам просто из-за того, что человек посещает веб-сайт, является серьезной проблемой.
Pointy
1
Он не более «уязвим», чем setInterval (popup, 0), который достаточно быстр, чтобы проблема в основном эквивалентна. Следует ли убрать точность в миллисекундах? кеннебек: ваш комментарий имеет смысл, спасибо.
mwcz 04

Ответы:

134

Как упоминалось в ответе Марка Рейхона, в современных браузерах есть API-интерфейс, который предоставляет скрипту данные о времени с субмиллисекундным разрешением: таймер высокого разрешения W3C , также известный как window.performance.now().

now()лучше традиционного Date.getTime()по двум важным причинам:

  1. now()представляет собой двойное значение с субмиллисекундным разрешением, которое представляет количество миллисекунд с начала навигации по странице. Он возвращает количество микросекунд в дробной части (например, значение 1000,123 составляет 1 секунду и 123 микросекунды).

  2. now()монотонно возрастает. Это важно , так как Date.getTime()может , возможно , прыгать вперед или назад даже при последующих вызовах. Примечательно, что если обновляется системное время ОС (например, синхронизация атомных часов), Date.getTime()оно также обновляется. now()всегда будет монотонно увеличиваться, поэтому на него не влияет системное время ОС - это всегда будет время настенных часов (при условии, что ваши настенные часы не атомные ...).

now()можно использовать практически везде new Date.getTime(), где + new Dateи Date.now()есть. Исключением является то, что Dateи now()времена не смешиваются, поскольку Dateосновано на unix-epoch (количество миллисекунд с 1970 года), а now()это количество миллисекунд с момента начала навигации по вашей странице (поэтому оно будет намного меньше Date).

now()поддерживается в стабильной версии Chrome, Firefox 15+ и IE10. Также доступно несколько полифиллов .

NicJ
источник
1
полифиллы, скорее всего, будут использовать Date.now (), так что это по-прежнему лучший вариант, учитывая IE9 и его миллионы пользователей, зачем тогда смешивать сторонние библиотеки
Виталий Терзиев
4
Мои настенные часы является атомарным.
programmer5000
4
new Date.getTime()не вещь. new Date().getTime()является.
The Qodesmith
Мне очень понравился этот ответ. Я провел несколько тестов и придумал пример, который вы можете вставить в свою консоль, чтобы увидеть, что при использовании этого все еще будут драматические коллизии. (обратите внимание, я получал 10% коллизий на хорошей машине даже при выполнении чего-то более дорогого, например, console.logна каждом прогоне) Трудно разобрать, но скопируйте весь выделенный код здесь:last=-11; same=0; runs=100; for(let i=0;i<runs;i++) { let now = performance.now(); console.log('.'); if (now === last) { same++; } last = now; } console.log(same, 'were the same');
bladnman
2
Возвращаясь к моему комментарию 2012 года . performance.now () теперь снова немного размыта обходными путями Meltdown / Spectre. Некоторые браузеры серьезно снизили производительность .now () по соображениям безопасности. Я думаю, что моя методика, вероятно, снова приобрела некоторую актуальность для огромного количества легитимных сценариев использования бенчмаркинга с учетом ограничений таймера. Тем не менее, в некоторых браузерах теперь есть некоторые функции / расширения для профилирования производительности разработчиков, которых не было в 2012 году.
Марк Рейхон
20

Теперь есть новый метод измерения микросекунд в javascript: http://gent.ilcore.com/2012/06/better-timer-for-javascript.html

Однако в прошлом я нашел грубый метод получения точности 0,1 миллисекунды в JavaScript из миллисекундного таймера. Невозможно? Нет. Продолжай читать:

Я провожу несколько высокоточных экспериментов, которые требуют точности таймера с самопроверкой, и обнаружил, что могу надежно получить точность 0,1 миллисекунды с определенными браузерами на определенных системах.

Я обнаружил, что в современных веб-браузерах с ускорением на GPU в быстрых системах (например, четырехъядерный i7, где несколько ядер простаивают, только окно браузера) - теперь я могу доверять таймерам с точностью до миллисекунд. Фактически, это стало настолько точным на простаивающей системе i7, что я смог надежно получить ту же самую миллисекунду после более чем 1000 попыток. Только когда я пытаюсь сделать что-то вроде загрузки дополнительной веб-страницы или чего-то другого, точность в миллисекундах ухудшается (и я могу успешно определить свою собственную ухудшенную точность, выполнив временную проверку до и после, чтобы увидеть, не мое время обработки внезапно увеличилось до 1 или более миллисекунд - это помогает мне аннулировать результаты, на которые, вероятно, слишком отрицательно повлияли колебания ЦП).

Он стал настолько точным в некоторых браузерах с ускорением на GPU в четырехъядерных системах i7 (когда окно браузера является единственным окном), что я обнаружил, что мне хотелось бы получить доступ к таймеру с точностью 0,1 мс в JavaScript, поскольку точность наконец-то достигла в некоторых высокопроизводительных системах просмотра, чтобы сделать такую ​​точность таймера целесообразной для определенных типов нишевых приложений, которые требуют высокой точности и где приложения могут самостоятельно проверять отклонения точности.

Очевидно, что если вы выполняете несколько проходов, вы можете просто выполнить несколько проходов (например, 10 проходов), а затем разделить их на 10, чтобы получить точность 0,1 миллисекунды. Это распространенный метод повышения точности - выполнить несколько проходов и разделить общее время на количество проходов.

ОДНАКО ... Если я могу выполнить только один проход определенного теста из-за необычно уникальной ситуации, я обнаружил, что могу получить точность 0,1 (а иногда и 0,01 мс), выполнив следующие действия:

Инициализация / калибровка:

  1. Запустите цикл занятости, чтобы дождаться увеличения таймера до следующей миллисекунды (выровняйте таймер с началом следующего миллисекундного интервала). Этот цикл занятости длится менее миллисекунды.
  2. Запустите еще один цикл занятости, чтобы увеличить счетчик, ожидая увеличения таймера. Счетчик показывает, сколько приращений счетчика произошло за одну миллисекунду. Этот цикл занятости длится одну полную миллисекунду.
  3. Повторяйте вышеуказанное, пока числа не станут сверхстабильными (время загрузки, JIT-компилятор и т. Д.). 4. ПРИМЕЧАНИЕ.: Стабильность числа дает вам достижимую точность в неработающей системе. Вы можете рассчитать дисперсию, если вам нужно самостоятельно проверить точность. В одних браузерах расхождения больше, в других - меньше. Больше на более быстрых системах и медленнее на более медленных системах. Последовательность тоже бывает разной. Вы можете сказать, какие браузеры более последовательны / точны, чем другие. Более медленные системы и загруженные системы приведут к большим различиям между проходами инициализации. Это может дать вам возможность отобразить предупреждающее сообщение, если браузер не обеспечивает достаточную точность для измерения 0,1 мс или 0,01 мс. Перекос таймера может быть проблемой, но некоторые целочисленные миллисекундные таймеры в некоторых системах увеличиваются довольно точно (прямо в точке), что приводит к очень согласованным значениям калибровки, которым вы можете доверять.
  4. Сохраните окончательное значение счетчика (или среднее значение последних нескольких проходов калибровки)

Тестирование одного прохода с точностью до миллисекунды:

  1. Запустите цикл занятости, чтобы дождаться увеличения таймера до следующей миллисекунды (выровняйте таймер с началом следующего миллисекундного интервала). Этот цикл занятости длится менее миллисекунды.
  2. Выполните задачу, которую хотите точно измерить временем.
  3. Проверить таймер. Это дает вам целое число миллисекунд.
  4. Запустите последний цикл занятости, чтобы увеличить счетчик, ожидая увеличения таймера. Этот цикл занятости длится менее миллисекунды.
  5. Разделите это значение счетчика на исходное значение счетчика от инициализации.
  6. Теперь у вас есть десятичная часть миллисекунд !!!!!!!!

ПРЕДУПРЕЖДЕНИЕ. Циклы занятости НЕ рекомендуются в веб-браузерах, но, к счастью, эти циклы занятости выполняются менее 1 миллисекунды каждый и выполняются всего несколько раз.

Такие переменные, как JIT-компиляция и колебания ЦП, добавляют огромные неточности, но если вы выполните несколько проходов инициализации, у вас будет полная динамическая перекомпиляция, и в конечном итоге счетчик установится на что-то очень точное. Убедитесь, что все циклы занятости - это одна и та же функция для всех случаев, чтобы различия в циклах занятости не приводили к различиям. Убедитесь, что все строки кода выполняются несколько раз, прежде чем вы начнете доверять результатам, чтобы позволить JIT-компиляторам уже стабилизироваться для полной динамической перекомпиляции (dynarec).

Фактически, я был свидетелем точности, приближающейся к микросекундам, на некоторых системах, но я пока не доверял этому. Но точность в 0,1 миллисекунды, похоже, работает достаточно надежно в простаивающей четырехъядерной системе, где я единственный браузер. Я пришел к научному тесту, в котором я мог выполнять только одноразовые проходы (из-за наличия уникальных переменных), и мне нужно было точно рассчитывать время каждого прохода, а не усреднять несколько повторных проходов, поэтому я и сделал это.

Я сделал несколько предварительных проходов и фиктивных проходов (также для урегулирования dynarec), чтобы проверить надежность точности 0,1 мс (оставалась стабильной в течение нескольких секунд), затем убрал руки с клавиатуры / мыши, пока выполнялся тест, затем сделал несколько пост-проходы для проверки надежности с точностью 0,1 мс (снова осталась стабильной). Это также подтверждает, что такие вещи, как изменения состояния питания или другие вещи, не происходили между до и после, что мешало результатам. Повторяйте предварительное и последующее тестирование между каждым тестом. После этого я был практически уверен, что промежуточные результаты были точными. Конечно, нет никаких гарантий, но это показывает, что в некоторых случаях в веб-браузере возможна точность <0,1 мс .

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

Марк Рейхон
источник
3
Раньше было сложно выполнить хронометраж с превосходной точностью, потому что все, что у нас было, было Date.now()или +new Date(). Но теперь у нас есть performance.now(). Хотя ясно, что вы нашли несколько классных способов расширить возможности, этот ответ по сути устарел. Кроме того, не рекомендуется ничего, связанного с занятыми циклами. Только не делай этого. Нам этого больше не нужно.
Стивен Лу
1
Большинство браузеров снизили точность своей реализации performance.now (), чтобы временно смягчить атаку по времени кэширования. Интересно, имеет ли этот ответ значение для исследований в области безопасности.
Qi Fan
2
Возвращаясь к моему собственному комментарию. Ух ты, я опубликовал это в 2012 году, задолго до performance.now (). Но теперь это снова немного размыто обходными путями Meltdown / Spectre. Некоторые браузеры серьезно снизили производительность .now () по соображениям безопасности. Я думаю, что вышеупомянутый метод, вероятно, снова приобрел некоторую актуальность для огромного количества легитимных сценариев использования бенчмаркинга с учетом ограничений таймера.
Марк Рейхон
3

В общем, ответ - «нет». Если вы используете JavaScript в некоторой серверной среде (то есть не в браузере), тогда все ставки отключены, и вы можете пытаться делать все, что захотите.

изменить - это старый ответ; стандарты прогрессируют, и новые средства доступны как решения проблемы точного времени. Тем не менее, следует помнить, что за пределами домена настоящей операционной системы реального времени обычный непривилегированный код имеет ограниченный контроль над своим доступом к вычислительным ресурсам. Измерение производительности - это не то же самое (обязательно), что прогнозирование производительности.

Заостренный
источник
2

Вот пример, показывающий мой таймер с высоким разрешением для node.js :

 function startTimer() {
   const time = process.hrtime();
   return time;
 }

 function endTimer(time) {
   function roundTo(decimalPlaces, numberToRound) {
     return +(Math.round(numberToRound + `e+${decimalPlaces}`)  + `e-${decimalPlaces}`);
   }
   const diff = process.hrtime(time);
   const NS_PER_SEC = 1e9;
   const result = (diff[0] * NS_PER_SEC + diff[1]); // Result in Nanoseconds
   const elapsed = result * 0.0000010;
   return roundTo(6, elapsed); // Result in milliseconds
 }

Использование:

 const start = startTimer();

 console.log('test');

 console.log(`Time since start: ${endTimer(start)} ms`);

Обычно вы можете использовать:

 console.time('Time since start');

 console.log('test');

 console.timeEnd('Time since start');

Если вы синхронизируете разделы кода, которые включают цикл, вы не можете получить доступ к значению console.timeEnd(), чтобы сложить результаты таймера вместе. Вы можете, но это становится неприятно, потому что вам нужно ввести значение вашей повторяющейся переменной, напримерi , и установить условие, чтобы определить, завершен ли цикл.

Вот пример, потому что это может быть полезно:

 const num = 10;

 console.time(`Time til ${num}`);

 for (let i = 0; i < num; i++) {
   console.log('test');
   if ((i+1) === num) { console.timeEnd(`Time til ${num}`); }
   console.log('...additional steps');
 }

Цитируйте: https://nodejs.org/api/process.html#process_process_hrtime_time.

agm1984
источник