Memoization is a technique for storing computed values in-memory. Essentially, it's a fancy name for in-memory cache.
Memoization is useful when you have potentially expensive or highly repetitive computations that need to be executed. Rather than re-running the computation on each iteration, your routine computes once and fetches already computed values from memory.
The example I've used in this benchmark is testing ISO date interval strings. Valid strings would have more variance than my test set, but you can see how memoization would be useful in cases like this where the result set is relatively fixed. In this case, a simple memoized match function is nearly six times as fast as JavaScript's built-in regular expression match, and three times faster than lodash's implementation.
> [email protected] bench /private/tmp/bench
> node ./index.js
SimpleMemoize#test x 6,102,561 ops/sec ±1.21% (92 runs sampled)
LodashMemoize#test x 1,810,809 ops/sec ±0.47% (96 runs sampled)
RegExp#test x 1,328,119 ops/sec ±0.42% (95 runs sampled)
Fastest is SimpleMemoize#test
The rules for when to and when not to memoize are the same as when and when not to cache, that is, a bit tricky. If the input to your memoized function is going to vary a lot, the chances of a "hit" are much lower. Therefore, the extra work to look up and store results may actually end up being slower than a non-memoized implementation. Speaking of storage, memoization is definitely not free! If the computed results are very large or highly variant, and your memoized function will reside in memory for the lifetime of your process you can run into resource exhaustion issues.
Additionally, memoization works best when the results of your operation do not need to be invalidated. That is, given an input, the output will always be the same. There are techniques for invalidation (lodash's implementation even has built-in support for this), but it does increase the complexity of what your application needs to manage.
As stated above, a memoized function works best when a given input always results in the same output. A simple implementation then follows these steps:
- Does the store already have a value for this input? If yes, return value, else, continue.
- Compute the value
- Store the value
- Return the value
In JavaScript, this could look like this:
const memoize = worker => {
const memoMap = new Map();
return i => {
if (memoMap.has(i)) {
return memoMap.get(i);
}
const res = worker(i);
memoMap.set(i, res);
return res;
}
}
const double = memoize(i => i * 2);
double(1); // 2, computed
double(1); // 2, not computed
This gist can be used to demonstrate the benefits of memoization. Make sure you have node.js v12+
on your machine, then download the gist, run npm i
then npm run bench
.