Timed Memoization

Certain operations are computationally expensive, but because their results might change over time or due to outside influences, they don't lend themselves to typical memoization — take for example getElementsByClassName. Here's a JavaScript timed memoization decorator / higher-order-function I made to help with these cases, which accepts an optional expiration argument in milliseconds.

function memoize (functor, expiration) {
	var memo = {};
	return function () {
		var key = Array.prototype.join.call(arguments, "§");
		if (key in memo)
			return memo[key];
		if (expiration)
			setTimeout(function () {delete memo[key];}, expiration);
		return memo[key] = functor.apply(this, arguments);
	};
}

This approach allows you to turn any function into a memoizing function. Note that return values are memoized for each set of arguments. However, due to technical constraints it's only reliable when the arguments are arrays or scalar values, but you could easily use e.g. a toJSON method rather than join to serialize objects as part of the cache key (at some additional overhead cost).

You can use the above code like this:

// Make a function which memoizes for 1000 milliseconds at a time
var fn = memoize(function () {
	Array(500000).join("."); // slow
	return true;
}, 1000);

…Or leave out the expiration argument to permanently memoize.

Here are a couple more posts on JavaScript memoization:

11 thoughts on “Timed Memoization”

  1. @Kris, Chiron modules are generally quite robust and awesome — I should have expected that you would’ve included a memoize decorator somewhere. But even though I hadn’t seen that one, I had seen the one by Keith Gaughan linked to above, so I tried to focus this post on the timed expiration aspect of it which I haven’t yet seen in other JavaScript libraries. Granted, that feature might only be useful in edge cases, but where it is I can envision significant performance savings that otherwise might be passed up.

  2. @Steve – I like your implementation although I’m struggling though to see how this could improve getElementsByClassName() functions except in limited circumstances. Care to provide an example?

    @Kris – I’d never heard of Chiron until recently. It looks very impressive. What do you plan to do with it? Is it being used anywhere?

  3. Dean, I agree that it could only improve getElementsByClassName functions in limited circumstances. The case I was specifically thinking of when I wrote that was code that invokes getElementsByClassName and runs onmouseover for a DOM node, thereby potentially being triggered thousands of times over a short amount of time. Memoization (timed or otherwise) would probably never be a good idea as a general purpose alteration for such functions, but depending on the nature of one’s application they might decide the benefits outweigh the risks. That said, the real reason I used that example was that I couldn’t think of a better one. 😉 I didn’t originally write this implementation for the ability to garbage collect cached values after a while, but that seemed like a potentially interesting extension that was easy to add.

  4. @Steve – Fair enough. I thought you had some clever implementation of getElementsByClassName() in mind.

    It’s still a cool function though. I might steal it and include it in base2. 😉

  5. @Dean sorry for the delay. Steve just let me know I missed your message. I’m using Chiron for a variety of projects including a browser-based MUD-like game, a remote Python shell, a log aggregation service, and some other data-visualization stuff. I’ve been promoting it as much as I can (the http://modulesjs.com website, presented at Barcamp LA a couple weeks ago http://cixar.com/barcamp for slides); it’s designed to be a platform for a “batteries included” JavaScript library. If it looks awesome, it’s partially because I read your base2 code to help inform its design, although I would still like to integrate more of your code.

    In recent news, I’m using memoize, and my Dict-like Cookie type in tandem to do cookie-based caching on a function.

    include(‘cookie.js’);
    include(‘cache.js’);
    this.fib = memoize(cookie, fib);

Leave a Reply

Your email address will not be published. Required fields are marked *