Seleccionar página

Limitaciones del uso de Date.now()

Cuando uno empieza a realizar los primeros análisis de rendimiento de su código en Javascript es muy común utilizar un par de variables, una antes y otras después del código que queremos comprobar, donde almacenamos la fecha y hora actual obtenida con Date.now() y luego comparamos ambas. No es una técnica incorrecta, pero en muchas ocasiones nuestro código se va a ejecutar con mayor velocidad del que somos capaces de capturar con Date.now(), es decir, obtenemos el mismo valor en las dos variables y por lo tanto el tiempo de ejecución que nos aparece es 0.

var start = new Date ();
var a     = 1000;
var end   = new Date ();
console.log (end - start);

Lo que sucede es que nuestro código a tardado menos de un milisegundo en ejecutarse y, por lo tanto, las dos variables tienen la misma fecha y hora. Date.now() devuelve el número de milisegundos pasados desde el 1 de enero de 1970 a las 0:00 hora UTC. Este método no dispone de mayor precisión que el milisegundo, yaunque nos parezca un periodo de tiempo muy pequeño, los motores modernos de Javascript pueden hacer muchas cosas en 1 milisegundo.

También tenemos que saber que la mayoría de las veces podemos confiar en la comparación de valores de vueltos por new Date(), aunque puedan tener una precisión limitada, pero en algunas ocasiones podemos obtener valores incorrectos por la actualización de la hora del sistema. Hay que tener en cuenta que el reloj del ordenador puede ser actualizado por el usuario o por algún servicio de sincronización de la hora del sistema como NTP y si comparamos dos valores tomados antes y después de esta actualización del reloj podemos, incluso, obtener valores negativos.

Tiempo de alta precisión, relativo y monótono

En 2012 -hace ya bastante tiempo- apareció en los navegadores el API performance.now() para obtener diferencias de tiempos con una precisión elevada (High Resolution Time). Este API está implementada en IE10, Firefox 15, Chrome 24, Safari 8 y Node 8.5 (en este último con require('perf_hooks').performance, que está publicado en forma experimental), por lo que podemos utilizarla sin temor a que no esté disponible en nuestro sistema.

var start = performance.now();
var a     = 1000;
var end   = performance.now();
console.log (end - start);

Las llamadas a performance.now() devuelven un valor de milisegundos, con decimales, con una precisión de milésima de milisegundo (millonesima parte de un segundo, o nanosegundo), por lo que su precisión es muy alta. Dan el tiempo desde el inicio de nuestro programa, no son una fecha y hora como tal, y por lo tanto sólo son útiles si los utilizados como marcas de tiempo, por ejemplo para analizar el rendimiento o hacer algún tipo de marca temporal de alta precisión.

La otra ventaja es que los valores de tiempo devueltos al llamar al método performance.now() son siempre monótonamente crecientes y no están sujetos a los ajustes, sesgo o desviación del reloj del sistema. La diferencia entre cualquiera de los dos valores obtenidos de performance.now() nunca puede ser negativa y crece de forma estable (monótona).

En Node, además de poder utilizar la función por medio del paquete perf_hooks, que en estos momentos está publicado en modo experimental, tenemos a nuestra disposición dos métodos muy sencillos para obtener un tiempo de alta precisión: process.hrtime() que devuelve una matriz con dos valores, uno con el valor en milisegundos y otro los nanosegundos y también el más moderno process.hrtime.bigint() que devuelve directamente un valor de tipo bigint con los nanosegundos.

Si queremos disponer de una función que funcione directamente tanto en navegador como en servidor, podemos construirla con mucha facilidad de esta manera:

/**
 * get high precision relative time
 * @returns {number}
 */
const hrtime =
  (typeof window !== 'undefined' &&
  typeof window.performance !== 'undefined' &&
  window.performance.now) ?
    window.performance.now.bind (window.performance) :
    (typeof require !== 'undefined') ?
      require ('perf_hooks').performance.now :
      function () {
        var h = process.hrtime ();
        return (h[ 0 ] + (h[ 1 ] / 1e9)) * 1000;
      };

var start = hrtime ();
for (var n = 0; n < 10000; n++) {
  var x = Math.log(n);
}
var end   = hrtime ();
console.log (end - start);

Fecha y hora de alta precisión

Aunque no suele ser necesario, si queremos obtener una fecha y hora de alta precision podemos partir de performance.now() y apoyarnos en performance.timeOrigin que tiene el valor inicial a partir del cual está calcuándose el valor devuelto por performance.now().

var start = performance.now() + performance.timeOrigin;
var a     = 1000;
var end   = performance.now() + performance.timeOrigin;
console.log ('start:', start, 'end:', end, 'Date.now()');

El problema es que performance.timeOrigin es relativamente reciente y es posible que no lo encontremos en todos los navegadores y tendríamos, en esos casos, que utilizar otras propiedades ahora deprecadas como performance.timing.navigationStart que, con un poco menos de precisión, son equivalentes.

Seguridad y tiempo de alta precisión

A fin de evitar algunas confusiones, queremos aclarar que el acceso a información precisa de tiempo es una necesidad común para muchas aplicaciones y no estamos haciendo nada incorrecto al usar estas API. Por ejemplo, son imprescindibles para la coordinación de elementos en animaciones o para realizar un análisis del rendimiento de las partes críticas del código.

El problema es que el acceso a esta información de tiempo alta precisión también puede ser utilizada con fines maliciosos por un atacante para obtener o inferir datos, de ahí que se haya producido un cierto debate sobre estas API y su nivel de precisión. Algunos de estos problemas de privacidad y seguridad consisten en que la alta precisión de estas API podrían permitir a un sitio web malintencionado diferenciar entre subconjuntos de usuarios y, en algunos casos extremos, identificar a un usuario en particular.

Algunos navegadores (especialmente Firefox) han incorporado mecanismos para reducir la precisión de estas API y evitar estas actividades de rastreo, pero no se han generalizado. En cualquier caso, a pesar de que puedan ser utilizadas para realizar algún tipo de rastreo, estas son una APIs perfectamente válidas y utilizables sin ningún tipo de prevención.

Novedades

customElements a fondo

Vamos a dar repaso general a customElements y todos sus métodos. Esta es una pieza clave para la creación de Custom Tag, uno de los pilares de los Web Components. Hemos intentado empezar por lo más básico e ir avanzando hasta describir algunos de sus comportamientos más complejos, además de describir algunas de las características más importantes de los Web Components.

Uso de jsRPC en una aplicación de ejemplo

Para poder comprender mejor cómo podemos utilizar la librería jsRPC hemos creado una aplicación de ejemplo en la que hemos utilizado el modelo RPC para que el front invoque funciones del servidor y para que los diferentes microservicios invoquen de forma remota funciones entre ellos.

Un completo sistema RPC en Javascript con sólo 100 líneas

La aparición de gRPC de Google ha hecho que vuelva a ponerse de moda los sistemas de Remote Procedure Calls. La mayoría de las implementaciones de RPC se han ajustado muy poco a las características idiomáticas de Javascript. Con jsRPC mostramos cómo desarrollar un completo sistema RPC utilizando las características de Javascript.

Usar correctamente el método sort()

En general no se hace un uso habitual del método .sort() de los Array y muchas ocasiones se desconoce cómo hacer funcionar este método de forma correcta. En este artículo os contaremos cómo funciona y cómo sacar partido a sus características.