Мікросекундна синхронізація в JavaScript


100

Чи є в JavaScript функції синхронізації з мікросекундною роздільною здатністю?

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

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

Оновлення: timer.js рекламує мікросекунду з роздільною здатністю, але вона просто помножує мілісекунди на 1000. Перевірено тестуванням та інспекцією коду. Розчарований. : [


2
Що ви намагаєтесь зробити у браузері, який вимагає мікросекундної точності? Загалом гарантії продуктивності поведінки браузерів просто не такі точні.
Юлій

4
Не станеться. Ви взагалі не можете довіряти точності мікросекунд, навіть якби вона існувала. Єдиний вагомий варіант використання, який я можу собі уявити, - це власні клієнти в chrome, але тоді ви не дбаєте про JS API. Також люблю ставитися до "Богоявлення" як до першокласного браузера та ігноруючи IE.
Raynos 04

6
"Отримання" часу у javascript займає деякий час, як і його повернення, і затримка збільшується, якщо ви знаходитесь на веб-сторінці, яка перемальовує або обробляє події. Я б навіть не розраховував на найближчу точність 10 мілісекунд.
kennebec

1
Як, скажімо, підкидання спливаючих вікон на надвисокій швидкості? По суті, проблема полягає в тому, що надати зовнішнім сторонам занадто великий доступ до машин користувачів, лише через те, що людина відвідує веб-сайт, є серйозною проблемою.
Пойнті

1
Він не є нічим більш "вразливим", ніж setInterval (спливаюче вікно, 0), що досить швидко, щоб проблема в основному була еквівалентною. Чи слід також видаляти точність мілісекунд? kennebec: ваш коментар має сенс, дякую.
mwcz

Відповіді:


134

Як зазначено у відповіді Марка Рейхона, у сучасних браузерах доступний API, який виставляє скрипту дані часу роздільної здатності, що перевищують мілісекунди: W3C High Resolution Timer , він же 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. Також є кілька поліфілів .


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. Деякі браузери серйозно погіршили performance.now () із міркувань безпеки. Я думаю, що моя техніка, мабуть, знову набула певної актуальності для величезної кількості багатьох законних випадків використання бенчмаркінгу, з урахуванням обмежень таймера. Тим не менш, деякі браузери тепер мають деякі функції / розширення профілювання продуктивності розробників, які не існували в 2012 році.
Марк Рейхон,

20

Зараз існує новий метод вимірювання мікросекунд у javascript: http://gent.ilcore.com/2012/06/better-timer-for-javascript.html

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

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

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

Це стало настільки точним у деяких браузерах з прискореним графічним процесором на чотирьохядерних системах 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 мілісекунди, схоже, працює досить надійно, в простою чотириядерної системи, де я єдина сторінка браузера. Я дійшов до наукового тесту, де я міг робити лише одноразові проходи (завдяки унікальним змінним, що трапляються), і мені потрібно було точно визначати час кожного проходу, а не усереднювати багаторазові повтори, тому я зробив це.

Я зробив кілька попередніх та фіктивних передач (також для врегулювання динареку), щоб перевірити надійність з точністю до 0,1 мс (залишався стабільним протягом декількох секунд), потім відвів руки від клавіатури / миші, поки проходив тест, а потім зробив кілька післяпроходження для перевірки надійності з точністю до 0,1 мс (знову залишається стабільним) Це також підтверджує, що такі речі, як зміни стану живлення чи інші речі, не відбувались до та після, перешкоджаючи результатам. Повторіть попередній і післятестовий випробування між кожним проходом тесту. Після цього я був майже практично впевнений, що результати між ними були точними. Звичайно, немає ніяких гарантій, але це свідчить про те, що в деяких випадках у веб-браузері можлива точність <0,1 мс .

Цей метод корисний лише у дуже і дуже нішевих випадках. Незважаючи на це, це буквально не буде на 100% безмежно гарантованим, ви можете отримати цілком надійну точність і навіть наукову точність, поєднуючись із декількома рівнями внутрішніх та зовнішніх перевірок.


3
Раніше було складно робити хронометраж з надзвичайною точністю, тому що все, що ми мали, було Date.now()або +new Date(). Але зараз ми маємо performance.now(). Хоча зрозуміло, що ви знайшли кілька цікавих способів зламати більше можливостей, ця відповідь по суті застаріла. Також не рекомендуйте нічого, що стосується зайнятих циклів. Тільки не роби цього. Нам більше цього не потрібно.
Steven Lu

1
Більшість браузерів знизили точність реалізації performance.now (), щоб тимчасово пом'якшити атаку синхронізації кешу. Цікаво, чи ця відповідь все ще має значення у дослідженнях безпеки.
Qi Fan

2
Перегляд мого власного коментаря. Ого, я розмістив вищезазначене у 2012 році задовго до performance.now (). Але тепер це трохи злегка розмито завдяки обхідним методам Meltdown / Spectre. Деякі браузери серйозно погіршили performance.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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.