Mikrosekunden-Timing in JavaScript

99

Gibt es in JavaScript Timing-Funktionen mit einer Auflösung von Mikrosekunden?

Ich kenne timer.js für Chrome und hoffe, dass es eine Lösung für andere benutzerfreundliche Browser wie Firefox, Safari, Opera, Epiphany, Konqueror usw. gibt. Ich bin nicht daran interessiert, einen IE zu unterstützen, sondern Antworten einschließlich IE sind willkommen.

(Angesichts der schlechten Genauigkeit des Millisekunden-Timings in JS halte ich bei diesem nicht den Atem an!)

Update: timer.js kündigt eine Mikrosekundenauflösung an, multipliziert jedoch einfach den Millisekundenwert mit 1.000. Verifiziert durch Testen und Code-Inspektion. Enttäuscht. : [

mwcz
quelle
2
Was versuchen Sie in einem Browser zu tun, der eine Mikrosekundengenauigkeit erfordert? Im Allgemeinen sind die Leistungsgarantien für das Verhalten von Browsern einfach nicht so genau.
Yuliy
4
Das wird nicht passieren. Sie können der Genauigkeit von Mikrosekunden überhaupt nicht vertrauen, selbst wenn sie vorhanden ist. Der einzige solide Anwendungsfall, den ich mir vorstellen kann, sind native Clients in Chrome, aber dann interessiert Sie die JS-API nicht. Ich liebe es auch, "Epiphany" als erstklassigen Browser zu behandeln und den IE zu ignorieren.
Raynos
6
Das Abrufen der Zeit in Javascript dauert einige Zeit, ebenso wie das Zurückgeben - und die Latenz erhöht sich, wenn Sie sich auf einer Webseite befinden, auf der Ereignisse neu gezeichnet oder verarbeitet werden. Ich würde nicht einmal mit der Genauigkeit von 10 Millisekunden rechnen.
kennebec
1
Zum Beispiel Popups mit superschneller Geschwindigkeit hochwerfen? Grundsätzlich besteht das Problem darin, dass es ein ernstes Problem ist, externen Parteien zu viel Zugriff auf Benutzercomputer zu gewähren, nur weil eine Person eine Website besucht.
Pointy
1
Es ist nicht "anfälliger" als setInterval (Popup, 0), was schnell genug ist, dass das Problem im Grunde gleichwertig ist. Sollte auch die Millisekundengenauigkeit entfernt werden? kennebec: dein kommentar macht sinn, danke.
MWCZ

Antworten:

134

Wie in Mark Rejhons Antwort erwähnt, ist in modernen Browsern eine API verfügbar, die Timing-Daten mit einer Auflösung von weniger als einer Millisekunde für Skripte verfügbar macht: der W3C High Resolution Timer , auch bekannt als window.performance.now().

now()ist Date.getTime()in zweierlei Hinsicht besser als das Traditionelle :

  1. now()ist ein Double mit einer Auflösung von Submillisekunden, das die Anzahl der Millisekunden seit Beginn der Seitennavigation darstellt. Es gibt die Anzahl der Mikrosekunden im Bruchteil zurück (z. B. ein Wert von 1000,123 ist 1 Sekunde und 123 Mikrosekunden).

  2. now()nimmt monoton zu. Dies ist wichtig, da es bei nachfolgenden Anrufen möglicherweise vorwärts oder sogar rückwärts springen Date.getTime()kann . Insbesondere wenn die Systemzeit des Betriebssystems aktualisiert wird (z. B. Atomuhrsynchronisation), wird dies ebenfalls aktualisiert. wird garantiert immer monoton ansteigen, so dass es nicht von der Systemzeit des Betriebssystems beeinflusst wird - es wird immer die Wanduhrzeit sein (vorausgesetzt, Ihre Wanduhr ist nicht atomar ...).Date.getTime()now()

now()kann an fast jedem Ort verwendet werden new Date.getTime(), + new Dateund Date.now()sind. Die Ausnahme ist , dass Dateund now()mal nicht mische, da Dateauf Basis Unix-Epoche (die Anzahl der Millisekunden seit 1970), während now()die Anzahl der Millisekunden seit Ihrer Seite der Navigation gestartet (so ist es viel kleiner als sein Date).

now()wird in Chrome Stable, Firefox 15+ und IE10 unterstützt. Es gibt auch mehrere Polyfills .

NicJ
quelle
1
Die Polyfills werden höchstwahrscheinlich Date.now () verwenden, daher ist dies immer noch die beste Option, wenn man den IE9 und seine Millionen von Benutzern berücksichtigt. Warum also dann eine Bibliothek von Drittanbietern mischen
Vitaliy Terziev
4
Meine Wanduhr ist atomar.
Programmierer5000
4
new Date.getTime()ist keine Sache. new Date().getTime()ist.
Der Qodesmith
Diese Antwort hat mir sehr gut gefallen. Ich habe einige Tests durchgeführt und ein Beispiel gefunden, das Sie in Ihre Konsole einfügen können, um festzustellen, dass dies bei Verwendung immer noch dramatische Kollisionen zur Folge hat. (Beachten Sie, dass ich 10% Kollisionen auf einer guten Maschine bekam, obwohl ich console.logbei jedem Lauf etwas so Teueres wie eine gemacht habe. ) Schwer zu erkennen, aber kopieren Sie den gesamten hervorgehobenen Code hier: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
1
Wiederholung meines Kommentars für das Jahr 2012 . performance.now () ist jetzt durch Meltdown / Spectre-Problemumgehungen wieder leicht verwirrt. Einige Browser haben die Leistung.now () aus Sicherheitsgründen erheblich beeinträchtigt. Ich denke, meine Technik hat wahrscheinlich wieder an Relevanz für eine Vielzahl legitimer Benchmarking-Anwendungsfälle gewonnen, die zeitlichen Einschränkungen unterliegen. Das heißt, einige Browser haben jetzt einige Funktionen / Erweiterungen zur Leistungsprofilerstellung für Entwickler, die 2012 nicht existierten.
Mark Rejhon
20

Es gibt jetzt eine neue Methode zum Messen von Mikrosekunden in Javascript: http://gent.ilcore.com/2012/06/better-timer-for-javascript.html

In der Vergangenheit habe ich jedoch eine grobe Methode gefunden, um aus einem Millisekunden-Timer eine Genauigkeit von 0,1 Millisekunden in JavaScript zu erhalten. Unmöglich? Nee. Weiter lesen:

Ich mache einige hochpräzise Experimente, die selbst überprüfte Timergenauigkeiten erfordern, und habe festgestellt, dass ich mit bestimmten Browsern auf bestimmten Systemen zuverlässig eine Genauigkeit von 0,1 Millisekunden erzielen konnte.

Ich habe festgestellt, dass in modernen GPU-beschleunigten Webbrowsern auf schnellen Systemen (z. B. i7 Quad Core, wo mehrere Kerne inaktiv sind, nur Browserfenster) - ich kann jetzt darauf vertrauen, dass die Timer millisekundengenau sind. Tatsächlich ist es auf einem i7-System im Leerlauf so genau geworden, dass ich über mehr als 1.000 Versuche zuverlässig genau dieselbe Millisekunde erhalten konnte. Nur wenn ich versuche, eine zusätzliche Webseite oder eine andere zu laden, verschlechtert sich die Millisekundengenauigkeit (und ich kann meine eigene verschlechterte Genauigkeit erfolgreich erfassen, indem ich eine Vorher-Nachher-Zeitprüfung durchführe, um festzustellen, ob Meine Verarbeitungszeit verlängerte sich plötzlich auf 1 oder mehr Millisekunden - dies hilft mir, Ergebnisse ungültig zu machen, die wahrscheinlich zu stark durch CPU-Schwankungen beeinträchtigt wurden.

Es ist in einigen GPU-beschleunigten Browsern auf i7 Quad-Core-Systemen so genau geworden (wenn das Browserfenster das einzige Fenster ist), dass ich mir gewünscht habe, ich könnte auf einen 0,1-ms-Präzisions-Timer in JavaScript zugreifen, da die Genauigkeit jetzt endlich ist Es gibt einige High-End-Browsersysteme, mit denen sich eine solche Timer-Präzision für bestimmte Arten von Nischenanwendungen lohnt, die eine hohe Präzision erfordern und bei denen die Anwendungen in der Lage sind, sich auf Genauigkeitsabweichungen zu überprüfen.

Wenn Sie mehrere Durchgänge ausführen, können Sie natürlich einfach mehrere Durchgänge ausführen (z. B. 10 Durchgänge) und dann durch 10 dividieren, um eine Genauigkeit von 0,1 Millisekunden zu erhalten. Dies ist eine gängige Methode, um eine bessere Präzision zu erzielen. Führen Sie mehrere Durchgänge durch und teilen Sie die Gesamtzeit durch die Anzahl der Durchgänge.

JEDOCH ... Wenn ich aufgrund einer ungewöhnlich einzigartigen Situation nur einen einzigen Benchmark-Durchgang eines bestimmten Tests durchführen kann, habe ich festgestellt, dass ich auf diese Weise eine Genauigkeit von 0,1 (und manchmal 0,01 ms) erreichen kann:

Initialisierung / Kalibrierung:

  1. Führen Sie eine Besetztschleife aus, um zu warten, bis der Timer auf die nächste Millisekunde erhöht wird (richten Sie den Timer auf den Beginn des nächsten Millisekundenintervalls aus). Diese Besetztschleife dauert weniger als eine Millisekunde.
  2. Führen Sie eine weitere Besetztschleife aus, um einen Zähler zu erhöhen, während Sie darauf warten, dass der Timer erhöht wird. Der Zähler gibt an, wie viele Zählerinkremente in einer Millisekunde aufgetreten sind. Diese belebte Schleife dauert eine volle Millisekunde.
  3. Wiederholen Sie den obigen Vorgang, bis die Zahlen extrem stabil sind (Ladezeit, JIT-Compiler usw.). 4. HINWEIS: Die Stabilität der Nummer gibt Ihnen Ihre erreichbare Präzision auf einem Leerlaufsystem. Sie können die Varianz berechnen, wenn Sie die Genauigkeit selbst überprüfen müssen. Die Abweichungen sind bei einigen Browsern größer und bei anderen Browsern kleiner. Größer auf schnelleren Systemen und langsamer auf langsameren Systemen. Die Konsistenz variiert ebenfalls. Sie können feststellen, welche Browser konsistenter / genauer sind als andere. Langsamere und ausgelastete Systeme führen zu größeren Abweichungen zwischen den Initialisierungsdurchläufen. Dies kann Ihnen die Möglichkeit geben, eine Warnmeldung anzuzeigen, wenn der Browser Ihnen nicht genügend Genauigkeit bietet, um Messungen von 0,1 ms oder 0,01 ms zu ermöglichen. Timer-Versatz kann ein Problem sein, aber einige ganzzahlige Millisekunden-Timer auf einigen Systemen werden ziemlich genau inkrementiert (genau auf den Punkt genau), was zu sehr konsistenten Kalibrierungswerten führt, denen Sie vertrauen können.
  4. Speichern Sie den endgültigen Zählerwert (oder den Durchschnitt der letzten Kalibrierungsdurchläufe).

Benchmarking eines Durchgangs mit einer Genauigkeit von weniger als einer Millisekunde:

  1. Führen Sie eine Besetztschleife aus, um zu warten, bis der Timer auf die nächste Millisekunde erhöht wird (richten Sie den Timer auf den Beginn des nächsten Millisekundenintervalls aus). Diese belebte Schleife dauert weniger als eine Millisekunde.
  2. Führen Sie die Aufgabe aus, für die Sie die Zeit genau messen möchten.
  3. Überprüfen Sie den Timer. Dies gibt Ihnen die ganzzahligen Millisekunden.
  4. Führen Sie eine letzte Besetztschleife aus, um einen Zähler zu erhöhen, während Sie darauf warten, dass der Timer erhöht wird. Diese belebte Schleife dauert weniger als eine Millisekunde.
  5. Teilen Sie diesen Zählerwert durch den ursprünglichen Zählerwert aus der Initialisierung.
  6. Jetzt hast du den Dezimalteil von Millisekunden !!!!!!!!

WARNUNG: Busy-Loops werden in Webbrowsern NICHT empfohlen. Glücklicherweise werden diese Busy-Loops jeweils weniger als 1 Millisekunde lang ausgeführt und nur sehr wenige Male ausgeführt.

Variablen wie die JIT-Kompilierung und CPU-Schwankungen führen zu massiven Ungenauigkeiten. Wenn Sie jedoch mehrere Initialisierungsdurchläufe ausführen, erhalten Sie eine vollständige dynamische Neukompilierung, und schließlich wird der Zähler auf etwas sehr Genaues eingestellt. Stellen Sie sicher, dass alle Besetztschleifen in allen Fällen genau dieselbe Funktion haben, damit Unterschiede in Besetztschleifen nicht zu Unterschieden führen. Stellen Sie sicher, dass alle Codezeilen mehrmals ausgeführt werden, bevor Sie den Ergebnissen vertrauen, damit sich JIT-Compiler bereits zu einer vollständigen dynamischen Neukompilierung (Dynarec) stabilisiert haben.

Tatsächlich habe ich auf bestimmten Systemen eine Präzision gesehen, die sich Mikrosekunden nähert , aber ich würde ihr noch nicht vertrauen. Aber die Genauigkeit von 0,1 Millisekunden scheint auf einem inaktiven Quad-Core-System, auf dem ich die einzige Browserseite bin, ziemlich zuverlässig zu funktionieren. Ich kam zu einem wissenschaftlichen Testfall, in dem ich nur einmalige Durchgänge durchführen konnte (aufgrund eindeutiger Variablen), und musste jeden Durchgang genau zeitlich festlegen, anstatt mehrere Wiederholungsdurchläufe zu mitteln. Deshalb habe ich dies getan.

Ich habe mehrere Pre-Pässe und Dummy-Pässe durchgeführt (auch um den Dynarec zu regeln), um die Zuverlässigkeit mit einer Genauigkeit von 0,1 ms zu überprüfen (blieb einige Sekunden lang solide), dann habe ich meine Hände von der Tastatur / Maus gelassen, während der Benchmark stattfand, und dann mehrere Nachdurchläufe zur Überprüfung der Zuverlässigkeit mit einer Genauigkeit von 0,1 ms (blieb wieder solide). Dies stellt auch sicher, dass Dinge wie Änderungen des Energiezustands oder andere Dinge zwischen dem Vorher und Nachher nicht aufgetreten sind und die Ergebnisse beeinträchtigen. Wiederholen Sie den Vor- und Nach-Test zwischen jedem einzelnen Benchmark-Durchgang. In diesem Zusammenhang war ich mir ziemlich sicher, dass die dazwischen liegenden Ergebnisse korrekt waren. Es gibt natürlich keine Garantie, aber es zeigt, dass in einem Webbrowser in einigen Fällen eine Genauigkeit von <0,1 ms möglich ist .

Diese Methode ist nur in sehr, sehr Nischenfällen nützlich . Trotzdem ist es buchstäblich nicht 100% ig garantiert, Sie können eine sehr vertrauenswürdige Genauigkeit und sogar wissenschaftliche Genauigkeit erzielen, wenn Sie es mit mehreren Ebenen interner und externer Überprüfungen kombinieren.

Mark Rejhon
quelle
3
Früher war es kompliziert, das Timing mit höchster Präzision durchzuführen, weil wir nur Date.now()oder hatten +new Date(). Aber jetzt haben wir performance.now(). Obwohl es klar ist, dass Sie einige coole Möglichkeiten gefunden haben, mehr Funktionen zu hacken, ist diese Antwort im Wesentlichen veraltet. Empfehlen Sie auch nichts im Zusammenhang mit Besetztschleifen. Tu das einfach nicht. Mehr brauchen wir nicht.
Steven Lu
1
Die meisten Browser haben die Genauigkeit ihrer Implementierung von performance.now () reduziert, um den Cache-Timing-Angriff vorübergehend zu verringern. Ich frage mich, ob diese Antwort in der Sicherheitsforschung noch Bedeutung hat.
Qi Fan
2
Ich besuche meinen eigenen Kommentar noch einmal. Wow, ich habe das oben genannte im Jahr 2012 lange vor performance.now () gepostet . Aber jetzt ist das durch Meltdown / Spectre-Problemumgehungen wieder etwas verwirrt. Einige Browser haben die Leistung.now () aus Sicherheitsgründen erheblich beeinträchtigt. Ich denke, dass die oben genannte Technik für eine Vielzahl legitimer Benchmarking-Anwendungsfälle, die Timer-Fuzz-Einschränkungen unterliegen, wahrscheinlich wieder an Relevanz gewonnen hat.
Mark Rejhon
3

Die Antwort lautet im Allgemeinen "Nein". Wenn Sie JavaScript in einer serverseitigen Umgebung verwenden (dh nicht in einem Browser), sind alle Wetten deaktiviert und Sie können versuchen, alles zu tun, was Sie wollen.

bearbeiten - diese Antwort ist alt; Die Standards wurden weiterentwickelt und neuere Einrichtungen stehen als Lösungen für das Problem der genauen Zeit zur Verfügung. Es sollte jedoch beachtet werden, dass gewöhnlicher nicht privilegierter Code außerhalb der Domäne eines echten Echtzeitbetriebssystems nur begrenzte Kontrolle über seinen Zugriff auf Rechenressourcen hat. Das Messen der Leistung ist nicht dasselbe (notwendigerweise) wie das Vorhersagen der Leistung.

Spitze
quelle
2

Hier ist ein Beispiel, das meinen hochauflösenden Timer für node.js zeigt :

 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
 }

Verwendung:

 const start = startTimer();

 console.log('test');

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

Normalerweise können Sie möglicherweise Folgendes verwenden:

 console.time('Time since start');

 console.log('test');

 console.timeEnd('Time since start');

Wenn Sie Codeabschnitte zeitlich festlegen, die eine Schleife beinhalten, können Sie nicht auf den Wert von zugreifen console.timeEnd(), um Ihre Timer-Ergebnisse zu addieren. Sie können, aber es wird unangenehm, weil Sie den Wert Ihrer iterierenden Variablen einfügen müssen, z. B. i, und eine Bedingung festlegen müssen, um festzustellen, ob die Schleife abgeschlossen ist.

Hier ist ein Beispiel, weil es nützlich sein kann:

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

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

agm1984
quelle