Mikrosekundowy czas w JavaScript


100

Czy są jakieś funkcje czasowe w JavaScript z rozdzielczością mikrosekund?

Znam timer.js dla Chrome i mam nadzieję, że będzie rozwiązanie dla innych przyjaznych przeglądarek, takich jak Firefox, Safari, Opera, Epiphany, Konqueror itp. Nie jestem zainteresowany obsługą żadnego IE, ale odpowiedzi, w tym IE są mile widziane.

(Biorąc pod uwagę słabą dokładność pomiaru czasu w milisekundach w JS, nie wstrzymuję oddechu!)

Aktualizacja: timer.js ogłasza rozdzielczość w mikrosekundach, ale po prostu mnoży odczyt milisekund przez 1000. Weryfikowane przez testy i kontrolę kodu. Rozczarowany. : [


2
Co próbujesz zrobić w przeglądarce, która wymaga mikrosekundowej dokładności? Ogólnie rzecz biorąc, gwarancje działania przeglądarek nie są tak precyzyjne.
Yuliy

4
Nie zdarzy się. Nie można w ogóle ufać dokładności mikro-sekundowej, nawet jeśli istniała. Jedynym solidnym przypadkiem użycia, jaki mogę sobie wyobrazić, są natywni klienci w chrome, ale wtedy nie przejmujesz się API JS. Również uwielbiam traktować „Epiphany” jako przeglądarkę pierwszej klasy i ignorować IE.
Raynos

6
„Pobranie” czasu w javascript zajmuje trochę czasu, podobnie jak jego zwrot - a opóźnienie wzrasta, jeśli jesteś na stronie, która przerysowuje lub obsługuje zdarzenia. Nie liczyłbym nawet na dokładność z dokładnością do najbliższych 10 milisekund.
kennebec

1
Na przykład wyrzucanie wyskakujących okienek z bardzo dużą prędkością? Zasadniczo problem polega na tym, że przyznanie stronom zewnętrznym zbyt dużego dostępu do maszyn użytkowników tylko z powodu faktu, że osoba odwiedza witrynę internetową, jest poważnym problemem.
Pointy

1
Nie jest bardziej "podatny na ataki" niż setInterval (popup, 0), który jest na tyle szybki, że problem jest w zasadzie równoważny. Czy należy również usunąć dokładność milisekundową? kennebec: twój komentarz ma sens, dziękuję.
mwcz

Odpowiedzi:


134

Jak wspomniano w odpowiedzi Marka Rejhona, w nowoczesnych przeglądarkach dostępny jest interfejs API, który udostępnia skryptowi dane czasowe o rozdzielczości poniżej milisekundy: W3C High Resolution Timer , inaczej window.performance.now().

now()jest lepszy od tradycyjnego Date.getTime()z dwóch ważnych powodów:

  1. now()jest liczbą podwójną z rozdzielczością submilisekundową, która reprezentuje liczbę milisekund od początku nawigacji strony. Zwraca liczbę mikrosekund w ułamku (np. Wartość 1000,123 to 1 sekunda i 123 mikrosekundy).

  2. now()rośnie monotonicznie. Jest to ważne, ponieważ Date.getTime()może ewentualnie skoczyć do przodu lub do tyłu nawet na kolejnych połączeń. Warto zauważyć, że aktualizowany jest także czas systemowy systemu operacyjnego (np. Synchronizacja zegara atomowego) Date.getTime(). now()zawsze rośnie monotonicznie, więc nie ma na niego wpływu czas systemowy - zawsze będzie to zegar ścienny (zakładając, że zegar ścienny nie jest atomowy ...).

now()może być stosowany w niemal każde miejsce, które new Date.getTime(), + new Datei Date.now()są. Wyjątkiem jest to, że czasy Datei now()czasy się nie mieszają, co Datejest oparte na epoce unix (liczba milisekund od 1970 r.), Podczas gdy now()jest to liczba milisekund od rozpoczęcia nawigacji po stronie (więc będzie znacznie mniejsza niż Date).

now()jest obsługiwany w wersji stabilnej Chrome, Firefox 15+ i IE10. Dostępnych jest również kilka polyfillów .


1
polyfills będą najprawdopodobniej używać Date.now (), więc jest to nadal najlepsza opcja, biorąc pod uwagę IE9 i miliony użytkowników, po co więc mieszanie biblioteki innej firmy
Vitaliy Terziev

4
Mój zegar ścienny jest atomowy.
programmer5000

4
new Date.getTime()nie jest rzeczą. new Date().getTime()jest.
The Qodesmith

Bardzo podobała mi się ta odpowiedź. Przeprowadziłem kilka testów i wymyśliłem przykład, który możesz rzucić na swoją konsolę, aby zobaczyć, że nadal będzie miał dramatyczne kolizje podczas korzystania z tego. (zauważ, że na dobrej maszynie console.loglast=-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');
zdarzało

2
Wracając do mojego komentarza z 2012 roku . performance.now () jest teraz ponownie nieco zamazana przez obejścia Meltdown / Spectre. Niektóre przeglądarki mają poważnie obniżoną wydajność. Now () ze względów bezpieczeństwa. Myślę, że moja technika prawdopodobnie ponownie odzyskała znaczenie dla wielu uzasadnionych przypadków użycia benchmarkingu, podlegających ograniczeniom timera-fuzz. To powiedziawszy, niektóre przeglądarki mają teraz pewne funkcje / rozszerzenia profilowania wydajności programistów, które nie istniały w 2012 roku.
Mark Rejhon,

20

Jest teraz nowa metoda pomiaru mikrosekund w javascript: http://gent.ilcore.com/2012/06/better-timer-for-javascript.html

Jednak w przeszłości znalazłem prymitywną metodę uzyskiwania precyzji 0,1 milisekundy w JavaScript z milisekundowego timera. Niemożliwy? Nie. Czytaj dalej:

Przeprowadzam eksperymenty z wysoką precyzją, które wymagają samodzielnego sprawdzenia dokładności timera i stwierdziłem, że jestem w stanie niezawodnie uzyskać dokładność 0,1 milisekundy w niektórych przeglądarkach w niektórych systemach.

Zauważyłem, że w nowoczesnych przeglądarkach internetowych z akceleracją GPU w szybkich systemach (np. Czterordzeniowy i7, gdzie kilka rdzeni jest bezczynnych, tylko okno przeglądarki) - mogę teraz ufać, że liczniki czasu będą działały z dokładnością do milisekund. W rzeczywistości stało się to tak dokładne w bezczynnym systemie i7, że byłem w stanie niezawodnie uzyskać dokładnie tę samą milisekundę, ponad 1000 prób. Tylko wtedy, gdy próbuję załadować dodatkową stronę internetową lub inne, dokładność milisekund spada (I jestem w stanie z powodzeniem złapać własną obniżoną dokładność, wykonując kontrolę czasu przed i po, aby sprawdzić, czy mój czas przetwarzania nagle wydłużył się do 1 lub więcej milisekund - pomaga mi to unieważnić wyniki, na które prawdopodobnie zbyt niekorzystny wpływ miały wahania wydajności procesora).

Stało się tak dokładne w niektórych przeglądarkach z akceleracją GPU w czterordzeniowych systemach i7 (kiedy okno przeglądarki jest jedynym oknem), że chciałbym mieć dostęp do precyzyjnego timera 0,1 ms w JavaScript, ponieważ dokładność jest wreszcie teraz w niektórych zaawansowanych systemach przeglądania, aby taka precyzja timera była opłacalna w niektórych typach zastosowań niszowych, które wymagają wysokiej precyzji, i w których aplikacje są w stanie samodzielnie zweryfikować odchylenia dokładności.

Oczywiście, jeśli wykonujesz kilka przebiegów, możesz po prostu wykonać wiele przebiegów (np. 10 przebiegów), a następnie podzielić przez 10, aby uzyskać dokładność 0,1 milisekundy. Jest to powszechna metoda uzyskiwania lepszej precyzji - wykonaj wiele przebiegów i podziel całkowity czas przez liczbę przebiegów.

JEDNAK ... Jeśli mogę wykonać tylko jeden test porównawczy określonego testu z powodu niezwykle wyjątkowej sytuacji, dowiedziałem się, że mogę uzyskać dokładność 0,1 (a czasami 0,01 ms), wykonując następujące czynności:

Inicjalizacja / kalibracja:

  1. Uruchom zajętą ​​pętlę, aby poczekać, aż licznik czasu zwiększy się do następnej milisekundy (wyrównaj licznik czasu do początku następnego interwału milisekundowego). Ta zajęta pętla trwa krócej niż milisekundę.
  2. Uruchom kolejną zajętą ​​pętlę, aby zwiększyć licznik podczas oczekiwania na zwiększenie licznika czasu. Licznik informuje, ile przyrostów licznika wystąpiło w ciągu jednej milisekundy. Ta zajęta pętla trwa jedną pełną milisekundę.
  3. Powtarzaj powyższe, aż liczby staną się bardzo stabilne (czas ładowania, kompilator JIT itp.). 4. UWAGA: Stabilność numeru zapewnia osiągalną precyzję w systemie bezczynności. Możesz obliczyć wariancję, jeśli chcesz samodzielnie sprawdzić dokładność. Rozbieżności są większe w niektórych przeglądarkach i mniejsze w innych przeglądarkach. Większy na szybszych systemach i wolniejszy na wolniejszych systemach. Konsystencja też jest różna. Możesz stwierdzić, które przeglądarki są bardziej spójne / dokładne niż inne. Wolniejsze systemy i obciążone systemy będą prowadzić do większych rozbieżności między przebiegami inicjalizacji. Może to dać Ci możliwość wyświetlenia komunikatu ostrzegawczego, jeśli przeglądarka nie zapewnia wystarczającej precyzji, aby umożliwić pomiary 0,1 ms lub 0,01 ms. Pochylenie timera może stanowić problem, ale niektóre liczniki czasu w milisekundach w niektórych systemach zwiększają się dość dokładnie (całkiem dokładnie na kropce), co daje bardzo spójne wartości kalibracji, którym można ufać.
  4. Zapisz końcową wartość licznika (lub średnią z kilku ostatnich przejść kalibracji)

Test porównawczy jednego przebiegu do dokładności poniżej milisekundy:

  1. Uruchom zajętą ​​pętlę, aby poczekać, aż licznik czasu zwiększy się do następnej milisekundy (wyrównaj licznik czasu do początku następnego interwału milisekundowego). Ta zajęta pętla trwa krócej niż milisekundę.
  2. Wykonaj zadanie, które chcesz dokładnie porównać z czasem.
  3. Sprawdź minutnik. To daje liczbę całkowitą w milisekundach.
  4. Uruchom ostatnią zajętą ​​pętlę, aby zwiększyć licznik, czekając na przyrost czasu. Ta zajęta pętla trwa krócej niż milisekundę.
  5. Podzielić tę wartość licznika przez oryginalną wartość licznika z inicjalizacji.
  6. Teraz masz dziesiętną część milisekund !!!!!!!!

OSTRZEŻENIE: Zajęte pętle NIE są zalecane w przeglądarkach internetowych, ale na szczęście te zajęte pętle działają krócej niż 1 milisekunda każda i są uruchamiane tylko kilka razy.

Zmienne, takie jak kompilacja JIT i wahania procesora, dodają ogromne niedokładności, ale jeśli uruchomisz kilka przebiegów inicjalizacyjnych, będziesz mieć pełną dynamiczną rekompilację, a ostatecznie licznik ustali się na coś bardzo dokładnego. Upewnij się, że wszystkie zajęte pętle mają dokładnie tę samą funkcję we wszystkich przypadkach, aby różnice w zajętych pętlach nie prowadziły do ​​różnic. Upewnij się, że wszystkie wiersze kodu są wykonywane kilka razy, zanim zaczniesz ufać wynikom, aby umożliwić kompilatorom JIT już ustabilizowanie do pełnej dynamicznej rekompilacji (dynarec).

W rzeczywistości byłem świadkiem zbliżania się precyzji do mikrosekund w niektórych systemach, ale jeszcze nie ufałbym temu. Ale dokładność 0,1 milisekundy wydaje się działać całkiem niezawodnie w bezczynnym czterordzeniowym systemie, w którym jestem jedyną stroną przeglądarki. Doszedłem do naukowego przypadku testowego, w którym mogłem wykonywać tylko jednorazowe przejścia (z powodu występujących unikalnych zmiennych) i musiałem precyzyjnie mierzyć czas każdego przebiegu, zamiast uśredniać wielokrotne powtórzenia, więc dlatego to zrobiłem.

Zrobiłem kilka przejść wstępnych i fałszywych (również w celu ustalenia dynamiki), aby zweryfikować niezawodność z dokładnością 0,1 ms (utrzymywałem się przez kilka sekund), potem trzymałem ręce z dala od klawiatury / myszy, podczas gdy wystąpił benchmark, a potem wykonałem kilka post-passy w celu zweryfikowania dokładności z dokładnością do 0,1 ms (ponownie pozostały stałe). To również weryfikuje, czy rzeczy takie jak zmiany stanu zasilania lub inne rzeczy nie wystąpiły między okresem przed i po, co wpływa na wyniki. Powtarzaj pre-test i post-test między każdym kolejnym przejściem benchmarku. W związku z tym byłem prawie pewien, że wyniki pomiędzy nimi były dokładne. Oczywiście nie ma gwarancji, ale pokazuje, że dokładność <0,1 ms jest możliwa w niektórych przypadkach w przeglądarce internetowej.

Ta metoda jest przydatna tylko w bardzo, bardzo niszowych przypadkach. Mimo to dosłownie nie będzie to w 100% w nieskończoność gwarantowane, możesz uzyskać bardzo wiarygodną dokładność, a nawet dokładność naukową w połączeniu z kilkoma warstwami weryfikacji wewnętrznych i zewnętrznych.


3
Kiedyś wykonywanie synchronizacji z najwyższą precyzją było skomplikowane, ponieważ jedyne, co mieliśmy, to Date.now()lub +new Date(). Ale teraz mamy performance.now(). Chociaż jasne jest, że znalazłeś fajne sposoby na hakowanie większej liczby funkcji, ta odpowiedź jest zasadniczo przestarzała. Nie polecaj też niczego związanego z zajętymi pętlami. Po prostu tego nie rób. Nie potrzebujemy tego więcej.
Steven Lu

1
Większość przeglądarek zmniejszyła precyzję implementacji performance.now (), aby tymczasowo złagodzić atak związany z synchronizacją pamięci podręcznej. Zastanawiam się, czy ta odpowiedź ma nadal znaczenie w badaniach nad bezpieczeństwem.
Qi Fan,

2
Wracając do własnego komentarza. Wow, opublikowałem to w roku 2012 na długo przed performance.now (). Ale teraz jest to trochę zamazane przez obejścia Meltdown / Spectre. Niektóre przeglądarki mają poważnie obniżoną wydajność. Now () ze względów bezpieczeństwa. Myślę, że powyższa technika prawdopodobnie ponownie odzyskała znaczenie dla wielu uzasadnionych przypadków użycia benchmarkingu, podlegających ograniczeniom timera-fuzz.
Mark Rejhon

3

Ogólnie rzecz biorąc, odpowiedź brzmi „nie”. Jeśli używasz JavaScript w jakimś środowisku po stronie serwera (to znaczy nie w przeglądarce), wtedy wszystkie zakłady są wyłączone i możesz spróbować zrobić wszystko, co chcesz.

edytuj - ta odpowiedź jest stara; standardy uległy poprawie i dostępne są nowsze urządzenia jako rozwiązania problemu dokładnego czasu. Mimo to należy pamiętać, że poza domeną prawdziwego systemu operacyjnego czasu rzeczywistego zwykły nieuprzywilejowany kod ma ograniczoną kontrolę nad dostępem do zasobów obliczeniowych. Mierzenie wydajności to nie to samo (koniecznie), co przewidywanie wydajności.


2

Oto przykład pokazujący mój licznik czasu w wysokiej rozdzielczości dla 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
 }

Stosowanie:

 const start = startTimer();

 console.log('test');

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

Zwykle możesz użyć:

 console.time('Time since start');

 console.log('test');

 console.timeEnd('Time since start');

Jeśli wykonujesz sekcje kodu czasowego, które obejmują zapętlenie, nie możesz uzyskać dostępu do wartości console.timeEnd()w celu dodania wyników czasomierza. Możesz, ale robi się nieprzyjemnie, ponieważ musisz wstrzyknąć wartość swojej zmiennej iteracyjnej, takiej jak i, i ustawić warunek, aby wykryć, czy pętla jest wykonana.

Oto przykład, ponieważ może być przydatny:

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

Cytuj: https://nodejs.org/api/process.html#process_process_hrtime_time

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.