Comparison of memoization implementations
JavaScript performance comparison
Preparation code
<script>
//memo6 - by @madrobby/thomas fuchs
Function.prototype.cached = function(){
var self = this, cache = {};
return function(arg){
if(arg in cache) {
//console.log('Cache hit for '+arg);
return cache[arg];
} else {
//console.log('Cache miss for '+arg);
return cache[arg] = self(arg);
}
}
}
//memo1
//underscore.js implem
function memoize1(func, hasher) {
var memo = {};
hasher || (hasher = function(value){
return value; //^this could be done better..surely..
});
return function() {
var key = hasher.apply(this, arguments);
return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
//memo2 - another variation with changes by @addyosmani
//based on initial work by thejit
//note: I'm aware of the perils of hanging the implem off
//fn, just want to find out if this version performs much better
function memoize2(fn) {
return function () {
var args = Array.prototype.slice.call(arguments),
hash = "",
i = args.length;
while(i--){
hash += (Object.prototype.toString.call({}) == Object.prototype.toString.call(args[i])) ? JSON.stringify(args[i]) : args[i];
fn.memoize = fn.memoize || {};
}
return (hash in fn.memoize) ? fn.memoize[hash] : fn.memoize[hash] = fn.apply(this, args);
};
}
//memo3 - unscriptables implem.
function memoize3(func, context) {
function memoizeArg (argPos) {
var cache = {};
return function () {
if (argPos == 0) {
if (!(arguments[argPos] in cache)) {
cache[arguments[argPos]] = func.apply(context, arguments);
}
return cache[arguments[argPos]];
}
else {
if (!(arguments[argPos] in cache)) {
cache[arguments[argPos]] = memoizeArg(argPos - 1);
}
return cache[arguments[argPos]].apply(this, arguments);
}
}
}
// JScript doesn't grok the arity property, but uses length instead
var arity = func.arity || func.length;
return memoizeArg(arity - 1);
}
//memo4 - by stevenlevithan
function memoize4(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);
};
}
//memo5 - @gbradley
function memoize5(fn){
var lookup={};
return function(){
var args=[].slice.call(arguments);
var key=JSON.stringify(args);
var result=lookup[key];
if (!result){
result=fn.apply(this,args);
lookup[key]=result;
}
return result;
};
}
function fib(x) {
if(x < 2) return 1; else return fib(x-1) + fib(x-2);
}
</script>
Test runner
Warning! For accurate results, please disable Firebug before running the tests. (Why?)
Java applet disabled.
| Test | Ops/sec | |
|---|---|---|
Memo 1 - underscore.js |
|
pending… |
Memo 2 - addy + thejit |
|
pending… |
Memo 3 - unscriptable |
|
pending… |
Memo 4 - stevenlevithan |
|
pending… |
Memo 5 - gbradley |
|
pending… |
Memo 6 - madrobby |
|
pending… |
Compare results of other browsers
Revisions
You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:
- Revision 1: published by Addy Osmani
- Revision 2: published by Addy Osmani
- Revision 3: published by Mariusz Nowak
- Revision 4: published by Addy Osmani
- Revision 5: published
- Revision 6: published by Mariusz Nowak
- Revision 7: published by Dmitry
- Revision 8: published by Dmitry Baranovskiy
- Revision 9: published by Addy Osmani
- Revision 10: published by Jamie Mason
- Revision 11: published by Jamie Mason
- Revision 12: published by Addy Osmani
- Revision 14: published by Paul Grenier
- Revision 15: published and last updated
- Revision 16: published
- Revision 17: published and last updated
- Revision 18: published by JasonG
- Revision 19: published by JasonG
- Revision 20: published by Asen Bozhilov
- Revision 22: published by jbdemonte
- Revision 23: published by nrn
- Revision 24: published by nrn
- Revision 25: published by nrn
- Revision 26: published
- Revision 27: published by Michael Lange
3 comments
I'll update the description later, but basically the setup for this is:
1) I'm defining each memoization implementation in sequence (eg. memoize1,memoize2,memoize3 etc..., with the exception of Fuchs implem which is hanging off the Function.prototype.
2) We're memoizing a fibonacci function called 'fib', which all implementations used have been previously tested with to ensure they're able to successfully memoize correctly.
3) Output is made twice, first to cache the result, the second time to lookup the cached result.
There are currently some issues with the test setup here. I think option 2 is faster than many of the others, but certainly not by the magnitude thats being reported. Looking into what's broken.
Believe I've finally tamed the tests. Using console for additional verification of results, but they're looking more sensible now.