BenchMark en JavaScript

En este artículo explicaremos cómo optimizar el rendimiento de JavaScript mediante un benchmark que compara funciones y/o bloques de código. Un benchmark es una prueba de rendimiento que permite comparar la eficiencia de distintos fragmentos de código o algoritmos. Su objetivo es medir el tiempo que tarda una función o bloque de código en ejecutarse y, a partir de ello, determinar cuál es más rápido o eficiente.

Cuando programamos, a veces necesitamos medir qué enfoque es más rápido para ejecutar una tarea concreta. Esto es especialmente útil si quieres optimizar un juego o una aplicación con muchos cálculos repetitivos. Hoy veremos un sencillo ejemplo con un benchmark en JavaScript, donde comprobaremos qué es más rápido al pasar parámetros a una función: si objetos, estructuras o variables individuales.

El código JavaScript

Imagina que tenemos dos valores {a, b} y queremos probar tres formas de pasar estos valores a una función para sumarlos:

let valores = {a: 10, b: 20};
let iterations = 1000000;

// Función 1: Pasar el objeto completo
function sumObject(data) {
    return data.a + data.b;
}

// Función 2: Usar la misma estructura
function sumEstruct({a, b}) {
    return a + b;
}

// Función 3: Pasar variables individuales
function sumVars(a, b) {
    return a + b;
}

Medir el rendimiento

Para medir el rendimiento de nuestro benchmark en JavaScript, el interior de cada función realiza la misma operación de suma, de modo que las diferencias que puedan haber, realmente será en el pase de parámetros. Para medir el tiempo que tarda cada función, usamos console.time() y console.timeEnd(), las cuales se encuentran explicadas en la serie de artículos la consola del navegador.

console.time("Objeto completo");
for (let i = 0; i < iterations; i++) {
    sumObject(valores);
}
console.timeEnd("Objeto completo");

Se hace lo mismo con las otras funciones y así podemos comparar cuál es más rápida. Al ejecutar el benchmark en cada una de las funciones, JavaScript internamente realiza las siguientes operaciones:

  • sumObject(valores) accede a las propiedades de un objeto pasado completo.
  • sumEstruct(valores) extrae las propiedades (x, y) del objeto y las asigna a variables locales.
  • sumVars(valores.x, valores.y) recibe los datos como variables independientes.

Normalmente, pasar variables individuales es ligeramente más rápido, pero la diferencia es pequeña a menos que estés haciendo millones de operaciones por segundo, como en un videojuego.

El BenchMark JavaScript completo

Puedes copiar y pegar este ejemplo de benchmark en JavaScript completo para modificarlo y usarlo en tus propias comprobaciones de código.

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Benchmark Funciones JS</title>
</head>
<body>
<h1>Benchmark para funciones JS</h1>
<p>Cada función se ejecuta en un bucle for de 1000000 ciclos</p>
<pre id="output"></pre>

<script>
  let valores = {a: 10, b: 20};
  let iterations = 1000000;

  function sumObject(data) {
      return data.a + data.b;
  }

  function sumEstruct({a, b}) {
      return a + b;
  }

  function sumVars(a, b) {
      return a + b;
  }

  function benchmark() {
      let result;

      console.time("Objeto completo");
      for (let i = 0; i < iterations; i++) {
          result = sumObject(valores);
      }
      console.timeEnd("Objeto completo");

      console.time("Objeto con estructura");
      for (let i = 0; i < iterations; i++) {
          result = sumEstruct(valores);
      }
      console.timeEnd("Objeto con estructura");

      console.time("Variables individuales");
      for (let i = 0; i < iterations; i++) {
          result = sumVars(valores.a, valores.b);
      }
      console.timeEnd("Variables individuales");

      document.getElementById("output").textContent = "Revisa la consola para los tiempos.";
  }

  benchmark();
</script>
</body>
</html>

Al ejecutarlo en tu navegador y abrir la consola del navegador deberías ver algo parecido a esto:

BenchMark en JavaScript

BenchMark en JavaScript

La finalidad de este benchmark en JavaScript es medir el rendimiento en el pase de parámetros y, según la forma de pasar los datos a una función, observamos:

  • Objetos planos: pasamos un único objeto y accedemos a sus propiedades dentro de la función (obj.x, obj.y). Es fácil de leer y mantener, pero cada acceso tiene un pequeño coste.
  • Estructuras de objetos: al recibir el objeto con {x, y}, JavaScript crea variables locales x e y a partir de las propiedades del objeto. Esto hace el código elegante y claro, pero añade un paso extra: primero accede al objeto y luego copia sus valores en variables locales.
  • Variables individuales: pasamos los valores directamente como parámetros (x, y). Aquí no hay que extraer nada: los datos llegan ya listos como variables locales. En teoría es la opción más rápida en millones de operaciones, pero la diferencia frente a objetos planos es mínima y depende del motor JS.

Al hacer pruebas con nuestro benchmark para código JavaScript es importante considerar el contexto. En juegos y simulaciones donde se ejecutan millones de operaciones en cada frame, estas diferencias sí se notan. Pero en la mayoría de aplicaciones normales, la claridad del código suele ser más importante que las microoptimizaciones.

En cualquier caso, simplemente cambiando los fragmentos de código a analizar, podrás hacer tus propias comprobaciones y optimizar tus algoritmos y funciones.

¡ Espero que este artículo sea de vuestro interés !

Deja un comentario