Cómo utilizar Memoize para almacenar en caché los resultados de las funciones de JavaScript y acelerar su código

Las funciones son una parte integral de la programación. Ayudan a agregar modularidad y reutilización a nuestro código.

Es bastante común dividir nuestro programa en partes usando funciones que podemos llamar más tarde para realizar alguna acción útil.

A veces, una función puede resultar costosa de llamar varias veces (por ejemplo, una función para calcular el factorial de un número). Pero hay una manera de optimizar dichas funciones y hacer que se ejecuten mucho más rápido: el almacenamiento en caché .

Por ejemplo, digamos que tenemos un functionpara devolver el factorial de un número:

function factorial(n) { // Calculations: n * (n-1) * (n-2) * ... (2) * (1) return factorial }

Genial, ahora encontremos factorial(50). La computadora realizará los cálculos y nos devolverá la respuesta final, ¡dulce!

Cuando esté hecho, busquemos factorial(51). La computadora vuelve a realizar una serie de cálculos y nos da el resultado, pero es posible que haya notado que ya estamos repitiendo una serie de pasos que podrían haberse evitado. Una forma optimizada sería:

factorial(51) = factorial(50) * 51

Pero nuestro functionrealiza los cálculos desde cero cada vez que se llama:

factorial(51) = 51 * 50 * 49 * ... * 2 * 1

¿No sería genial si de alguna manera nuestra factorialfunción pudiera recordar los valores de sus cálculos anteriores y usarlos para acelerar la ejecución?

Llega la memorización , una forma de functionrecordar (almacenar en caché) los resultados. Ahora que tiene una comprensión básica de lo que estamos tratando de lograr, aquí tiene una definición formal:

La memorización es una técnica de optimización que se utiliza principalmente para acelerar los programas informáticos almacenando los resultados de costosas llamadas a funciones y devolviendo el resultado almacenado en caché cuando se repiten las mismas entradas.

Memorizar en términos simples significa memorizar o almacenar en la memoria. Una función memorizada suele ser más rápida porque si la función se llama posteriormente con los valores anteriores, en lugar de ejecutar la función, obtendríamos el resultado de la caché.

Así es como se vería una función simple memorizada (y aquí hay un CodePen en caso de que desee interactuar con él) :

// a simple function to add something const add = (n) => (n + 10); add(9); // a simple memoized function to add something const memoizedAdd = () => { let cache = {}; return (n) => { if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = n + 10; cache[n] = result; return result; } } } // returned function from memoizedAdd const newAdd = memoizedAdd(); console.log(newAdd(9)); // calculated console.log(newAdd(9)); // cached

Conclusiones de la memorización

Algunas conclusiones del código anterior son:

  • memoizedAdddevuelve un functionque se invoca más tarde. Esto es posible porque en JavaScript, las funciones son objetos de primera clase que nos permiten usarlas como funciones de orden superior y devolver otra función.
  • cachepuede recordar sus valores ya que la función devuelta tiene un cierre sobre ella.
  • Es esencial que la función memorizada sea pura. Una función pura devolverá la misma salida para una entrada en particular sin importar cuántas veces se llame, lo que hace que el cachetrabajo sea el esperado.

Escribiendo tu propia memoizefunción

El código anterior funciona bien, pero ¿y si quisiéramos convertir cualquier función en una función memorizada?

A continuación, se explica cómo escribir su propia función de memoizar (codepen):

// a simple pure function to get a value adding 10 const add = (n) => (n + 10); console.log('Simple call', add(3)); // a simple memoize function that takes in a function // and returns a memoized function const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; // just taking one argument here if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = fn(n); cache[n] = result; return result; } } } // creating a memoized function for the 'add' pure function const memoizedAdd = memoize(add); console.log(memoizedAdd(3)); // calculated console.log(memoizedAdd(3)); // cached console.log(memoizedAdd(4)); // calculated console.log(memoizedAdd(4)); // cached

¡Eso es genial! Esta memoizefunción simple envolverá cualquier simple functionen un equivalente memorizado. El código funciona bien para funciones simples y se puede modificar fácilmente para manejar cualquier número argumentssegún sus necesidades. Otra alternativa es hacer uso de algunas bibliotecas de facto como:

  • Lodash's _.memoize(func, [resolver])
  • @memoizeDecoradores ES7 de decko

Memorización de funciones recursivas

Si intenta pasar una función recursiva a la memoizefunción anterior o _.memoizedesde Lodash, los resultados no serán los esperados, ya que la función recursiva en sus llamadas posteriores terminará llamándose a sí misma en lugar de a la función memorizada, por lo que no hará uso de cache.

Solo asegúrese de que su función recursiva llame a la función memorizada. Así es como puede modificar un ejemplo factorial de libro de texto (codepen):

// same memoize function from before const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; if (n in cache) { console.log('Fetching from cache', n); return cache[n]; } else { console.log('Calculating result', n); let result = fn(n); cache[n] = result; return result; } } } const factorial = memoize( (x) => { if (x === 0) { return 1; } else { return x * factorial(x - 1); } } ); console.log(factorial(5)); // calculated console.log(factorial(6)); // calculated for 6 and cached for 5

Algunos puntos a tener en cuenta de este código:

  • La factorialfunción llama de forma recursiva a una versión memorizada de sí misma.
  • La función memorizada almacena en caché los valores de factoriales anteriores, lo que mejora significativamente los cálculos, ya que se pueden reutilizar. factorial(6) = 6 * factorial(5)

¿La memorización es lo mismo que el almacenamiento en caché?

Sí, algo así. La memorización es en realidad un tipo específico de almacenamiento en caché. Si bien el almacenamiento en caché puede referirse en general a cualquier técnica de almacenamiento (como el almacenamiento en caché HTTP) para uso futuro, la memorización implica específicamente almacenar en caché los valores de retorno de a function.

Cuándo memorizar sus funciones

Aunque puede parecer que la memorización se puede usar con todas las funciones, en realidad tiene casos de uso limitados:

  • Para memorizar una función, debe ser pura para que los valores de retorno sean los mismos para las mismas entradas cada vez
  • La memorización es una compensación entre espacio adicional y velocidad adicional y, por lo tanto, solo es significativo para funciones que tienen un rango de entrada limitado, de modo que los valores almacenados en caché se pueden utilizar con más frecuencia.
  • Puede parecer que debería memorizar sus llamadas a la API; sin embargo, no es necesario porque el navegador las almacena automáticamente en caché. Consulte Almacenamiento en caché HTTP para obtener más detalles.
  • El mejor caso de uso que encontré para funciones memorizadas es para funciones computacionales pesadas que pueden mejorar significativamente el rendimiento (factorial y fibonacci no son realmente buenos ejemplos del mundo real)
  • Si le gusta React / Redux, puede consultar reselect, que utiliza un selector memorizado para asegurarse de que los cálculos solo suceden cuando ocurre un cambio en una parte relacionada del árbol de estado.

Otras lecturas

The following links can be useful if you would like to know more about some of the topics from this article in more detail:

  • Higher order functions in JavaScript
  • Closures in JavaScript
  • Pure functions
  • Lodash’s _.memoize docs and source code
  • More memoization examples here and here
  • reactjs/reselect

I hope this article was useful for you, and you’ve gained a better understanding of memoization in JavaScript :)

You may follow me on twitter for latest updates. I've also started posting more recent posts on my personal blog.