Synchronisation de la microseconde en JavaScript


100

Existe-t-il des fonctions de synchronisation en JavaScript avec une résolution de l'ordre de la microseconde?

Je suis au courant de timer.js pour Chrome, et espère qu'il y aura une solution pour les autres navigateurs comme Firefox, amical, Safari, Opera, Epiphany, Konqueror, etc. Je ne suis pas intéressé à soutenir toute IE, mais les réponses , y compris IE sont les bienvenus.

(Compte tenu de la mauvaise précision du timing à la milliseconde en JS, je ne retiens pas mon souffle sur celui-ci!)

Mise à jour: timer.js annonce une résolution en microsecondes, mais il multiplie simplement la lecture en millisecondes par 1000. Vérifié par des tests et une inspection du code. Déçu. : [


2
Qu'essayez-vous de faire dans un navigateur qui nécessite une précision de l'ordre de la microseconde? En général, les garanties de performance du comportement des navigateurs ne sont tout simplement pas si précises.
Yuliy

4
Ça n'arrivera pas. Vous ne pouvez pas du tout faire confiance à la précision de la micro seconde, même si elle existait. Le seul cas d'utilisation solide que je puisse imaginer est celui des clients natifs dans Chrome, mais vous ne vous souciez pas de l'API JS. Aimant également traiter "Epiphany" comme un navigateur de première classe et ignorer IE.
Raynos le

6
«Obtenir» le temps en javascript prend un certain temps, tout comme le retourner - et la latence augmente si vous êtes sur une page Web qui redessine ou gère des événements. Je ne compterais même pas sur la précision de 10 millisecondes la plus proche.
kennebec

1
Comme, disons, lancer des popups à très haute vitesse? Fondamentalement, le problème est que le fait de donner trop d'accès aux machines des utilisateurs simplement du fait qu'une personne visite un site Web est un problème sérieux.
Pointy le

1
Ce n'est pas plus "vulnérable" que setInterval (popup, 0), qui est suffisamment rapide pour que le problème soit fondamentalement équivalent. La précision à la milliseconde doit-elle également être supprimée? kennebec: ton commentaire a du sens, merci.
mwcz le

Réponses:


134

Comme évoqué dans la réponse de Mark Rejhon, il existe une API disponible dans les navigateurs modernes qui expose des données de synchronisation de résolution inférieure à la milliseconde au script: le W3C High Resolution Timer , aka window.performance.now().

now()est meilleur que le traditionnel Date.getTime()de deux manières importantes:

  1. now()est un double avec une résolution submilliseconde qui représente le nombre de millisecondes depuis le début de la navigation de la page. Il renvoie le nombre de microsecondes dans la fraction (par exemple, une valeur de 1000.123 correspond à 1 seconde et 123 microsecondes).

  2. now()augmente de façon monotone. Ceci est important car Date.getTime()peut éventuellement sauter en avant ou même en arrière sur les appels suivants. Notamment, si l'heure système du système d'exploitation est mise à jour (par exemple, la synchronisation de l'horloge atomique), elle l' Date.getTime()est également. now()Il est garanti qu'il augmente toujours de manière monotone, donc il n'est pas affecté par l'heure système du système d'exploitation - ce sera toujours l'heure de l'horloge murale (en supposant que votre horloge murale n'est pas atomique ...).

now()peut être utilisé dans presque tous les endroits qui new Date.getTime(), + new Dateet Date.now()sont. L'exception est que Dateet les now()temps ne se mélangent pas, comme Datec'est basé sur unix-epoch (le nombre de millisecondes depuis 1970), alors que now()c'est le nombre de millisecondes depuis le début de la navigation de votre page (donc il sera beaucoup plus petit que Date).

now()est pris en charge dans Chrome stable, Firefox 15+ et IE10. Il existe également plusieurs polyfills disponibles.


1
les polyfills utiliseront très probablement Date.now (), c'est donc toujours la meilleure option compte tenu de l'IE9 et de ses millions d'utilisateurs, pourquoi mélanger une bibliothèque tierce alors
Vitaliy Terziev

4
Mon horloge murale est atomique.
programmer5000

4
new Date.getTime()n'est pas une chose. new Date().getTime()est.
The Qodesmith

J'ai vraiment aimé cette réponse. J'ai effectué quelques tests et j'ai trouvé un exemple que vous pouvez insérer dans votre console pour voir que cela aura encore des collisions dramatiques lors de l'utilisation de cela. (notez que j'obtenais 10% de collisions sur une bonne machine même en faisant quelque chose d'aussi cher qu'à console.logchaque exécution) Difficile à comprendre mais copiez tout le code en surbrillance ici: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
Revisitant mon commentaire de l' année 2012 . performance.now () est maintenant un peu floue à nouveau par les solutions de contournement Meltdown / Spectre. Certains navigateurs ont sérieusement dégradé performance.now () pour des raisons de sécurité. Je pense que ma technique a probablement retrouvé une certaine pertinence pour un grand nombre de cas d'utilisation de benchmarking légitimes, soumis à des limitations de minuterie-fuzz. Cela dit, certains navigateurs ont maintenant des fonctionnalités / extensions de profilage des performances des développeurs qui n'existaient pas en 2012.
Mark Rejhon

20

Il existe maintenant une nouvelle méthode de mesure des microsecondes en javascript: http://gent.ilcore.com/2012/06/better-timer-for-javascript.html

Cependant, dans le passé, j'ai trouvé une méthode rudimentaire pour obtenir une précision de 0,1 milliseconde en JavaScript à partir d'un minuteur en millisecondes. Impossible? Nan. Continue de lire:

Je fais des expériences de haute précision qui nécessitent des précisions de minuterie auto-vérifiées, et j'ai découvert que j'étais en mesure d'obtenir une précision de 0,1 milliseconde avec certains navigateurs sur certains systèmes.

J'ai trouvé que dans les navigateurs Web modernes accélérés par GPU sur des systèmes rapides (par exemple, i7 quad core, où plusieurs cœurs sont inactifs, seule fenêtre de navigateur) - je peux maintenant faire confiance aux minuteries pour être précises à la milliseconde. En fait, il est devenu si précis sur un système i7 inactif que j'ai pu obtenir de manière fiable la même milliseconde, sur plus de 1000 tentatives. Ce n'est que lorsque j'essaie de faire des choses comme charger une page Web supplémentaire, ou autre, la précision à la milliseconde se dégrade (et je suis capable de capturer avec succès ma propre précision dégradée en effectuant un contrôle de l'heure avant et après, pour voir si mon temps de traitement s'est soudainement allongé à 1 milliseconde ou plus - cela m'aide à invalider les résultats qui ont probablement été trop affectés par les fluctuations du processeur).

Il est devenu si précis dans certains navigateurs accélérés par GPU sur les systèmes quad-core i7 (lorsque la fenêtre du navigateur est la seule fenêtre), que j'ai trouvé que je souhaitais pouvoir accéder à un minuteur de précision de 0,1 ms en JavaScript, car la précision est enfin maintenant il existe sur certains systèmes de navigation haut de gamme pour rendre une telle précision de minuterie utile pour certains types d'applications de niche qui nécessitent une haute précision, et où les applications sont capables d'auto-vérifier les écarts de précision.

Évidemment, si vous faites plusieurs passes, vous pouvez simplement exécuter plusieurs passes (par exemple 10 passes) puis diviser par 10 pour obtenir une précision de 0,1 milliseconde. C'est une méthode courante pour obtenir une meilleure précision - effectuez plusieurs passes et divisez le temps total par le nombre de passes.

CEPENDANT ... Si je ne peux faire qu'un seul test de référence d'un test spécifique en raison d'une situation inhabituellement unique, j'ai découvert que je pouvais obtenir une précision de 0,1 (et parfois 0,01 ms) en faisant ceci:

Initialisation / étalonnage:

  1. Exécutez une boucle occupée pour attendre que le minuteur s'incrémente à la milliseconde suivante (aligner le minuteur sur le début de l'intervalle de milliseconde suivant). Cette boucle occupée dure moins d'une milliseconde.
  2. Exécutez une autre boucle occupée pour incrémenter un compteur en attendant que le minuteur s'incrémente. Le compteur vous indique combien d'incréments de compteur se sont produits en une milliseconde. Cette boucle occupée dure une milliseconde complète.
  3. Répétez ce qui précède, jusqu'à ce que les nombres deviennent ultra-stables (temps de chargement, compilateur JIT, etc.). 4. REMARQUE: La stabilité du nombre vous donne votre précision atteignable sur un système inactif. Vous pouvez calculer la variance si vous avez besoin d'auto-vérifier la précision. Les écarts sont plus importants sur certains navigateurs et plus petits sur d'autres navigateurs. Plus gros sur les systèmes plus rapides et plus lent sur les systèmes plus lents. La cohérence varie également. Vous pouvez dire quels navigateurs sont plus cohérents / précis que d'autres. Des systèmes plus lents et des systèmes occupés entraîneront des écarts plus importants entre les passes d'initialisation. Cela peut vous donner l'occasion d'afficher un message d'avertissement si le navigateur ne vous donne pas suffisamment de précision pour autoriser des mesures de 0,1 ms ou 0,01 ms. Le décalage de la minuterie peut être un problème, mais certains minuteries entières en millisecondes sur certains systèmes s'incrémentent assez précisément (tout à fait exactement sur le point), ce qui se traduira par des valeurs d'étalonnage très cohérentes auxquelles vous pouvez faire confiance.
  4. Enregistrer la valeur finale du compteur (ou la moyenne des dernières passes d'étalonnage)

Analyse comparative d'une passe à une précision inférieure à la milliseconde:

  1. Exécutez une boucle occupée pour attendre que le minuteur s'incrémente à la milliseconde suivante (aligner le minuteur sur le début de l'intervalle de milliseconde suivant). Cette boucle occupée dure moins d'une milliseconde.
  2. Exécutez la tâche que vous souhaitez comparer précisément le temps.
  3. Vérifiez la minuterie. Cela vous donne les millisecondes entières.
  4. Exécutez une dernière boucle occupée pour incrémenter un compteur en attendant que le minuteur s'incrémente. Cette boucle occupée dure moins d'une milliseconde.
  5. Divisez cette valeur de compteur par la valeur de compteur d'origine de l'initialisation.
  6. Vous avez maintenant la partie décimale des millisecondes !!!!!!!!

AVERTISSEMENT: les boucles occupées ne sont PAS recommandées dans les navigateurs Web, mais heureusement, ces boucles occupées durent moins de 1 milliseconde chacune et ne sont exécutées que très peu de fois.

Des variables telles que la compilation JIT et les fluctuations du processeur ajoutent des inexactitudes massives, mais si vous exécutez plusieurs passes d'initialisation, vous aurez une recompilation dynamique complète, et finalement le compteur se réglera sur quelque chose de très précis. Assurez-vous que toutes les boucles occupées ont exactement la même fonction dans tous les cas, de sorte que les différences dans les boucles occupées ne conduisent pas à des différences. Assurez-vous que toutes les lignes de code sont exécutées plusieurs fois avant de commencer à faire confiance aux résultats, pour permettre aux compilateurs JIT d'être déjà stabilisés à une recompilation dynamique complète (dynarec).

En fait, j'ai été témoin d'une précision approchant les microsecondes sur certains systèmes, mais je ne lui ferais pas encore confiance. Mais la précision de 0,1 milliseconde semble fonctionner de manière assez fiable, sur un système quad-core inactif où je suis la seule page de navigateur. Je suis arrivé à un cas de test scientifique où je ne pouvais faire que des passes uniques (en raison de variables uniques), et j'avais besoin de chronométrer précisément chaque passe, plutôt que de calculer la moyenne de plusieurs passes répétées, c'est pourquoi j'ai fait cela.

J'ai fait plusieurs pré-passes et passes factices (également pour régler le dynarec), pour vérifier la fiabilité de précision de 0,1 ms (resté solide pendant plusieurs secondes), puis j'ai gardé les mains sur le clavier / la souris, pendant que le benchmark se produisait, puis j'ai fait plusieurs post-passes pour vérifier la fiabilité d'une précision de 0,1 ms (resté solide à nouveau). Cela vérifie également que des choses telles que les changements d'état de l'alimentation, ou d'autres choses, ne se sont pas produites entre l'avant et l'après, interférant avec les résultats. Répétez le pré-test et le post-test entre chaque passage de référence. Sur ce, j'étais pratiquement certain que les résultats intermédiaires étaient exacts. Il n'y a pas de garantie, bien sûr, mais cela montre qu'une précision précise <0,1 ms est possible dans certains cas dans un navigateur Web.

Cette méthode n'est utile que dans des cas très, très niches . Même dans ce cas, il ne sera littéralement pas garanti à 100% à l'infini, vous pouvez gagner une précision assez fiable, et même une précision scientifique, lorsqu'il est combiné avec plusieurs couches de vérifications internes et externes.


3
Auparavant, il était compliqué de chronométrer avec une précision supérieure parce que tout ce que nous avions était Date.now()ou +new Date(). Mais maintenant nous avons performance.now(). Bien qu'il soit clair que vous avez trouvé des moyens intéressants de pirater plus de fonctionnalités, cette réponse est essentiellement obsolète. De plus, ne recommandez rien concernant les boucles très fréquentées. Ne fais pas ça. Nous n'avons pas besoin de plus de cela.
Steven Lu

1
La plupart des navigateurs ont réduit la précision de leur implémentation performance.now () pour atténuer temporairement l'attaque de synchronisation du cache. Je me demande si cette réponse a encore de l'importance dans la recherche sur la sécurité.
Qi Fan

2
Revisitant mon propre commentaire. Wow, j'ai posté ce qui précède en 2012 bien avant performance.now (). Mais maintenant, c'est un peu flou à nouveau légèrement par les solutions de contournement Meltdown / Spectre. Certains navigateurs ont sérieusement dégradé performance.now () pour des raisons de sécurité. Je pense que la technique ci-dessus a probablement retrouvé une certaine pertinence pour un grand nombre de cas d'utilisation de benchmarking légitimes, soumis à des limitations de minuterie-fuzz.
Mark Rejhon

3

La réponse est "non", en général. Si vous utilisez JavaScript dans un environnement côté serveur (c'est-à-dire pas dans un navigateur), tous les paris sont désactivés et vous pouvez essayer de faire tout ce que vous voulez.

modifier - cette réponse est ancienne; les normes ont progressé et de nouvelles installations sont disponibles pour résoudre le problème de la précision du temps. Même ainsi, il ne faut pas oublier qu'en dehors du domaine d'un véritable système d'exploitation en temps réel, le code ordinaire non privilégié a un contrôle limité sur son accès aux ressources de calcul. Mesurer la performance n'est pas (nécessairement) la même chose que prédire la performance.


2

Voici un exemple montrant mon minuteur haute résolution pour 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
 }

Usage:

 const start = startTimer();

 console.log('test');

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

Normalement, vous pourrez peut-être utiliser:

 console.time('Time since start');

 console.log('test');

 console.timeEnd('Time since start');

Si vous chronométrez des sections de code qui impliquent une boucle, vous ne pouvez pas accéder à la valeur de console.timeEnd()pour additionner les résultats de votre minuterie. Vous pouvez, mais cela devient désagréable car vous devez injecter la valeur de votre variable itérante, telle que i, et définir une condition pour détecter si la boucle est terminée.

Voici un exemple car il peut être utile:

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

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.