Tiempo de microsegundo en JavaScript

100

¿Hay funciones de sincronización en JavaScript con resolución de microsegundos?

Soy consciente de timer.js para Chrome y espero que haya una solución para otros navegadores amigables, como Firefox, Safari, Opera, Epiphany, Konqueror, etc. No estoy interesado en admitir ningún IE, pero las respuestas incluyen IE son bienvenidos.

(Dada la poca precisión de la sincronización de milisegundos en JS, ¡no estoy conteniendo la respiración en este caso!)

Actualización: timer.js anuncia una resolución de microsegundos, pero simplemente multiplica la lectura de milisegundos por 1,000. Verificado mediante pruebas e inspección de código. Decepcionado. : [

mwcz
fuente
2
¿Qué está intentando hacer en un navegador que requiere una precisión de microsegundos? En general, las garantías de rendimiento del comportamiento de los navegadores no son tan precisas.
Yuliy
4
No va a pasar. No puede confiar en la precisión de los microsegundos, incluso si existiera. El único caso de uso sólido que puedo imaginar son los clientes nativos en Chrome, pero luego no te importa la API de JS. También me encanta tratar "Epiphany" como un navegador de primera clase e ignorar IE.
Raynos
6
'Obtener' el tiempo en javascript lleva algo de tiempo, al igual que devolverlo, y la latencia aumenta si está en una página web que está redibujando o manejando eventos. Ni siquiera contaría con la precisión de 10 milisegundos más cercana.
kennebec
1
¿Como, digamos, lanzar ventanas emergentes a una velocidad súper alta? Básicamente, el problema es que dar demasiado acceso a terceros a las máquinas de los usuarios simplemente por el hecho de que una persona visita un sitio web es un problema grave.
Puntiagudo
1
No es más "vulnerable" que setInterval (popup, 0), que es lo suficientemente rápido como para que el problema sea básicamente equivalente. ¿Debería eliminarse también la precisión de milisegundos? kennebec: tu comentario tiene sentido, gracias.
mwcz

Respuestas:

134

Como se menciona en la respuesta de Mark Rejhon, hay una API disponible en los navegadores modernos que expone datos de tiempo de resolución de menos de milisegundos al script: el temporizador de alta resolución W3C , también conocido como window.performance.now().

now()es mejor que el tradicional Date.getTime()de dos formas importantes:

  1. now()es un doble con resolución de submilisegundos que representa el número de milisegundos desde el inicio de la navegación de la página. Devuelve el número de microsegundos en la fracción (por ejemplo, un valor de 1000,123 es 1 segundo y 123 microsegundos).

  2. now()está aumentando monótonamente. Esto es importante ya Date.getTime()que posiblemente puede avanzar o incluso retroceder en llamadas posteriores. En particular, si se actualiza la hora del sistema del sistema operativo (por ejemplo, sincronización del reloj atómico), Date.getTime()también se actualiza. now()se garantiza que siempre aumentará de manera monótona, por lo que no se verá afectado por la hora del sistema del sistema operativo; siempre será la hora del reloj de pared (suponiendo que su reloj de pared no sea atómico ...).

now()se puede utilizar en casi todos los lugares que new Date.getTime(), + new Datey Date.now()están. La excepción es que Datey los now()tiempos no se mezclan, ya que Datese basa en unix-epoch (el número de milisegundos desde 1970), mientras que now()es el número de milisegundos desde que comenzó la navegación de la página (por lo que será mucho menor que Date).

now()es compatible con Chrome estable, Firefox 15+ e IE10. También hay varios polyfills disponibles.

NicJ
fuente
1
los polyfills probablemente usarán Date.now (), por lo que esta sigue siendo la mejor opción considerando el IE9 y sus millones de usuarios, ¿por qué mezclar una biblioteca de terceros entonces
Vitaliy Terziev
4
Mi reloj de pared es atómico.
programador5000
4
new Date.getTime()no es una cosa. new Date().getTime()es.
The Qodesmith
Realmente me gustó esta respuesta. Ejecuté algunas pruebas y se me ocurrió un ejemplo que puede colocar en su consola para ver que esto todavía tendrá colisiones dramáticas al usar esto. (tenga en cuenta que estaba obteniendo un 10% de colisiones en una buena máquina incluso haciendo algo tan costoso como console.logen cada ejecución) Es difícil de entender, pero copie todo el código resaltado aquí: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
Revisando mi comentario del año 2012 . performance.now () ahora está un poco borroso nuevamente por las soluciones alternativas de Meltdown / Spectre. Algunos navegadores han degradado seriamente el performance.now () debido a razones de seguridad. Creo que mi técnica probablemente ha recuperado algo de relevancia nuevamente para una gran cantidad de casos de uso legítimos de evaluaciones comparativas, sujetos a limitaciones de temporización. Dicho esto, algunos navegadores ahora tienen algunas características / extensiones de perfiles de rendimiento para desarrolladores que no existían en 2012.
Mark Rejhon
20

Ahora hay un nuevo método para medir microsegundos en javascript: http://gent.ilcore.com/2012/06/better-timer-for-javascript.html

Sin embargo, en el pasado, encontré un método tosco para obtener una precisión de 0.1 milisegundos en JavaScript con un temporizador de milisegundos. ¿Imposible? No Sigue leyendo:

Estoy haciendo algunos experimentos de alta precisión que requieren precisiones de temporizador autoverificadas, y descubrí que pude obtener de manera confiable una precisión de 0.1 milisegundos con ciertos navegadores en ciertos sistemas.

He descubierto que en los navegadores web modernos acelerados por GPU en sistemas rápidos (por ejemplo, i7 de cuatro núcleos, donde varios núcleos están inactivos, solo ventana del navegador), ahora puedo confiar en que los temporizadores tienen una precisión de milisegundos. De hecho, se ha vuelto tan preciso en un sistema i7 inactivo que he podido obtener de manera confiable exactamente el mismo milisegundo, en más de 1,000 intentos. Solo cuando intento hacer cosas como cargar una página web adicional u otra, la precisión de milisegundos se degrada (y puedo detectar con éxito mi propia precisión degradada haciendo una verificación de tiempo antes y después, para ver si mi tiempo de procesamiento se alargó repentinamente a 1 o más milisegundos; esto me ayuda a invalidar los resultados que probablemente se hayan visto afectados demasiado negativamente por las fluctuaciones de la CPU).

Se ha vuelto tan preciso en algunos navegadores acelerados por GPU en sistemas i7 de cuatro núcleos (cuando la ventana del navegador es la única ventana), que descubrí que deseaba poder acceder a un temporizador de precisión de 0.1ms en JavaScript, ya que la precisión finalmente es ahora existen en algunos sistemas de navegación de alta gama para hacer que la precisión del temporizador valga la pena para ciertos tipos de aplicaciones de nicho que requieren alta precisión, y donde las aplicaciones pueden autoverificarse para detectar desviaciones de precisión.

Obviamente, si está haciendo varias pasadas, simplemente puede ejecutar varias pasadas (por ejemplo, 10 pasadas) y luego dividir por 10 para obtener una precisión de 0,1 milisegundos. Ese es un método común para obtener una mejor precisión: realice varias pasadas y divida el tiempo total por la cantidad de pasadas.

SIN EMBARGO ... Si solo puedo hacer una única prueba comparativa de una prueba específica debido a una situación inusualmente única, descubrí que puedo obtener una precisión de 0.1 (y a veces 0.01ms) al hacer esto:

Inicialización / Calibración:

  1. Ejecute un ciclo ocupado para esperar hasta que el temporizador se incremente al siguiente milisegundo (alinee el temporizador al comienzo del siguiente intervalo de milisegundos). Este ciclo ocupado dura menos de un milisegundo.
  2. Ejecute otro ciclo ocupado para incrementar un contador mientras espera que el temporizador se incremente. El contador le dice cuántos incrementos de contador ocurrieron en un milisegundo. Este bucle ocupado dura un milisegundo completo.
  3. Repita lo anterior, hasta que los números se vuelvan ultraestables (tiempo de carga, compilador JIT, etc.). 4. NOTA: La estabilidad del número le da su precisión alcanzable en un sistema inactivo. Puede calcular la varianza, si necesita autocomprobar la precisión. Las variaciones son mayores en algunos navegadores y menores en otros navegadores. Más grande en sistemas más rápidos y más lento en sistemas más lentos. La consistencia también varía. Puede saber qué navegadores son más consistentes / precisos que otros. Los sistemas más lentos y los sistemas ocupados conducirán a mayores variaciones entre las pasadas de inicialización. Esto puede darle la oportunidad de mostrar un mensaje de advertencia si el navegador no le brinda suficiente precisión para permitir mediciones de 0.1ms o 0.01ms. La desviación del temporizador puede ser un problema, pero algunos temporizadores de milisegundos enteros en algunos sistemas aumentan con bastante precisión (bastante justo en el punto), lo que dará como resultado valores de calibración muy consistentes en los que puede confiar.
  4. Guarde el valor final del contador (o el promedio de las últimas pasadas de calibración)

Evaluación comparativa de una pasada con precisión de menos de milisegundos:

  1. Ejecute un ciclo ocupado para esperar hasta que el temporizador se incremente al siguiente milisegundo (alinee el temporizador al comienzo del siguiente intervalo de milisegundos). Este bucle ocupado dura menos de un milisegundo.
  2. Ejecute la tarea que desee para comparar el tiempo con precisión.
  3. Comprueba el temporizador. Esto le da los milisegundos enteros.
  4. Ejecute un ciclo ocupado final para incrementar un contador mientras espera que el temporizador se incremente. Este bucle ocupado dura menos de un milisegundo.
  5. Divida este valor del contador por el valor del contador original desde la inicialización.
  6. Ahora tienes la parte decimal de milisegundos !!!!!!!!

ADVERTENCIA: NO se recomiendan los bucles ocupados en los navegadores web, pero, afortunadamente, estos bucles ocupados se ejecutan durante menos de 1 milisegundo cada uno y solo se ejecutan unas pocas veces.

Las variables como la compilación JIT y las fluctuaciones de la CPU agregan inexactitudes masivas, pero si ejecuta varias pasadas de inicialización, tendrá una recompilación dinámica completa y, finalmente, el contador se asentará en algo muy preciso. Asegúrese de que todos los bucles ocupados tengan exactamente la misma función para todos los casos, de modo que las diferencias en los bucles ocupados no den lugar a diferencias. Asegúrese de que todas las líneas de código se ejecuten varias veces antes de comenzar a confiar en los resultados, para permitir que los compiladores JIT ya se hayan estabilizado en una recompilación dinámica completa (dynarec).

De hecho, fui testigo de la precisión acercándose a los microsegundos en ciertos sistemas, pero todavía no me fiaría de ella. Pero la precisión de 0,1 milisegundos parece funcionar de manera bastante confiable, en un sistema de cuatro núcleos inactivo donde soy la única página del navegador. Llegué a un caso de prueba científica en el que solo podía hacer pases únicos (debido a que se producían variables únicas), y necesitaba cronometrar con precisión cada pase, en lugar de promediar múltiples pases repetidos, por eso hice esto.

Hice varios pases previos y pases ficticios (también para ajustar el dynarec), para verificar la confiabilidad de la precisión de 0,1 ms (permaneció sólido durante varios segundos), luego mantuve mis manos fuera del teclado / mouse, mientras se realizaba el punto de referencia, luego hice varios pasadas posteriores para verificar la confiabilidad de una precisión de 0,1 ms (se mantuvo sólido nuevamente). Esto también verifica que cosas como cambios de estado de energía u otras cosas, no ocurrieron entre el antes y el después, lo que interfiere con los resultados. Repita la prueba previa y la prueba posterior entre cada pasada de referencia. Sobre esto, estaba prácticamente seguro de que los resultados intermedios eran precisos. No hay garantía, por supuesto, pero demuestra que en algunos casos es posible una precisión de <0,1 ms en un navegador web.

Este método solo es útil en casos muy específicos . Aun así, literalmente no será 100% infinitamente garantizado, puede obtener una precisión bastante confiable e incluso una precisión científica cuando se combina con varias capas de verificaciones internas y externas.

Mark Rejhon
fuente
3
Solía ​​ser complicado cronometrar con una precisión superior porque todo lo que teníamos era Date.now()o +new Date(). Pero ahora tenemos performance.now(). Si bien está claro que ha encontrado algunas formas geniales de piratear más capacidades, esta respuesta es esencialmente obsoleta. Además, no recomiende nada relacionado con bucles ocupados. Simplemente no hagas eso. No necesitamos más de eso.
Steven Lu
1
La mayoría de los navegadores redujeron la precisión de su implementación performance.now () para mitigar temporalmente el ataque de tiempo de caché. Me pregunto si esta respuesta todavía tiene importancia en la investigación de seguridad.
Qi Fan
2
Revisando mi propio comentario. Vaya, publiqué lo anterior en el año 2012 mucho antes de performance.now (). Pero ahora eso está un poco confundido nuevamente por las soluciones de Meltdown / Spectre. Algunos navegadores han degradado seriamente el performance.now () debido a razones de seguridad. Creo que la técnica anterior probablemente ha recuperado algo de relevancia nuevamente para una gran cantidad de casos de uso legítimos de evaluación comparativa, sujetos a limitaciones de fuzz del temporizador.
Mark Rejhon
3

La respuesta es "no", en general. Si está utilizando JavaScript en algún entorno del lado del servidor (es decir, no en un navegador), entonces todas las apuestas están canceladas y puede intentar hacer lo que quiera.

editar - esta respuesta es antigua; los estándares han progresado y hay nuevas instalaciones disponibles como soluciones al problema de la hora exacta. Aun así, debe recordarse que fuera del dominio de un verdadero sistema operativo en tiempo real, el código ordinario sin privilegios tiene un control limitado sobre su acceso a los recursos informáticos. Medir el rendimiento no es lo mismo (necesariamente) que predecir el rendimiento.

Puntiagudo
fuente
2

Aquí hay un ejemplo que muestra mi temporizador de alta resolución para 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
 }

Uso:

 const start = startTimer();

 console.log('test');

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

Normalmente, es posible que pueda utilizar:

 console.time('Time since start');

 console.log('test');

 console.timeEnd('Time since start');

Si está cronometrando secciones de código que implican bucles, no puede obtener acceso al valor de console.timeEnd()para sumar los resultados del temporizador. Puede, pero se pone desagradable porque tiene que inyectar el valor de su variable iterativa, como i, y establecer una condición para detectar si el ciclo está terminado.

Aquí tienes un ejemplo porque puede ser útil:

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

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

agm1984
fuente