奇妙的memoizer
Thu, 25 Dec 2008 11:26:06 +0800在Douglas Crockford的書《JavaScript: 優良部份》以及演講「Ajax Performance」中,看到memoizer這個奇妙的東西,利用他以及適當地調整遞迴函數,可以把遞迴函數改成用iteration的方式執行,當計算的參數較大時,效能改進很明顯。
memoizer的原理是,把每次計算的結果記錄下來,提供下次計算使用,這樣函數就不用每次都計算到遞迴結束,減少的冗餘計算很多,所以可以大幅改善效能,但是又能使用遞迴函數簡潔的好處。
下面這個例子依次用到memoizer, Y combinator以及遞迴函數原本的方式,做費氏數列的運算:
<html>
<script>
var memoizer = function (memo, fundamental) {
var shell = function (n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fundamental(shell, n);
memo[n] = result;
}
return result;
};
return shell;
};
var Y = function(g) {
return (function(x) {
return g(function(n) {
return x(x)(n);
});
})(function(x) {
return g(function(n) {
return x(x)(n);
});
});
};
</script>
<body>
<script>
var count = 0;
var fab = memoizer([0,1], function(f,n){count++;return f(n-1)+f(n-2);});
alert(fab(10));
alert(count);
var count1 = 0;
var fab1 = Y(
function(f){
return function(n){
count1++;
if(n==0)
return 0;
if(n<3)
return 1;
return f(n-1) + f(n-2);
};
});
alert(fab1(10));
alert(count1);
var count2 = 0;
var fab2 = function(n) {
count2++;
if(n===0)
return 0;
if(n<3)
return 1;
return fab2(n-1) + fab2(n-2);
};
alert(fab2(10));
alert(count2);
</script>
</body>
</html>
從執行結果可以看出來,使用memoizer做了9次函數呼叫,同時使用Y combinator或是遞迴的狀況下呼叫了177次,效果非常可觀。Crockford在演講中舉的是fab(40),如果靠遞迴要做331160280次呼叫,而使用memoizer只要做39次。
今天是耶誕節,Crockford在他的blog上邀請不同宗教的人一同歡度。所以應景地把這篇叫做「奇妙」的memoizer吧。