Speed up your JavaScript, Part 3
Recursion is the enemy of fast-running scripts. Too much recursion can cause the browser to grind to a halt or quit unexpectedly, and so must be addressed a serious performance problem in JavaScript. In part 2 of this series, I wrote briefly about handling too much recursion in a function through memoization. Memoization is a technique for caching previously calculated values so that they need not be recalculated; when a recursive function is doing such a calculation, memoization is incredibly useful. The memoizer I presented was Crockford’s, and is useful primarily for recursive functions that return integers. All recursive functions, of course, don’t return integers. A more generic memoizer() function can be created to deal with any type of recursive function:
function memoizer(fundamental, cache){
cache = cache || {}
var shell = function(arg){
if (!cache.hasOwnProperty(arg)){
cache[arg] = fundamental(shell, arg)
}
return cache[arg];
};
return shell;
}
This version of the function is a bit different than Crockford’s. First, the order of arguments has been reversed with the original function as the first argument and an optional cache object as the second argument. Not all recursive functions are seeded with initial information, so making that argument optional makes sense. Inside, I’ve changed the caching data type from an array to an object, which makes this version applicable to recursive functions that return non-integer results. Inside the shell function, I’m using the hasOwnProperty() method to see if the argument already has a cache entry. This is safer than testing if the type of value isn’t undefined since undefined is a valid return value. Example usage with the previous Fibonacci example:
var fibonacci =
memoizer(function (recur, n) {
return recur(n - 1) + recur(n - 2);
}, {"0":0, "1":1});
Once again, a call to fibonacci(40) results in only 40 calls of the original function instead of 331,160,280. Memoization works great for recursive algorithms with a strictly defined result set. There are, however, other recursive algorithms that don’t lend themselves to optimization through memoization.
One of my professors in college insisted that anything written using recursion could also be written using iteration if necessary. Indeed, recursion and iteration are often considered remedies for one another when one is seen as a problem. The techniques for converting a recursive algorithm into an iterative algorithm are the same regardless of the programming language; the importance in JavaScript is greater, though, because the resources of the execution environment are so restrictive. Consider a typical recursive algorithm such as a merge sort. In JavaScript, it may be written like this:
function merge(left, right){
var result = [];
while (left.length > 0 && right.length > 0){
if (left[0] < right[0]){
result.push(left.shift());
} else {
result.push(right.shift());
}
}
return result.concat(left).concat(right);
}
//recursive merge sort algorithm
function mergeSort(items){
if (items.length == 1) {
return items;
}
var middle = Math.floor(items.length / 2),
left = items.slice(0, middle),
right = items.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
Calling the mergeSort() function on an array returns an array of the items sorted in correct order. Note that for each call to mergeSort() there are two recursive calls. This algorithm won’t benefit from memoization because each result is only calculated once and, therefore, caching the results doesn’t help. If you were to call mergeSort() on an array with 100 items, there would be 199 calls total; a 1,000 item array would result in 1,999 calls. The solution in this case is to convert the recursive algorithm into an iterative one, which means introducing some loops (algorithm credit: List Processing: Sort Again, Naturally):
//iterative merge sort algorithm
function mergeSort(items){
if (items.length == 1) {
return items;
}
var work = [];
for (var i=0, len=items.length; i < len; i++){
work.push([items[i]]);
}
work.push([]); //in case of odd number of items
for (var lim=len; lim > 1; lim = Math.floor((lim+1)/2)){
for (var j=0,k=0; k < lim; j++, k+=2){
work[j] = merge(work[k], work[k+1]);
}
work[j] = []; //in case of odd number of items
}
return work[0];
}
This implementation of the merge sort algorithm uses a series of loops instead of recursion to sort the array. Since merge sort works by first breaking down an array into several one-item arrays, this method does that explicitly instead of implicitly via recursive calls. The work array is initially an array of one-item arrays. The loops enable the merging of two arrays at a time, placing the result back into the work array. When the function has done its job, the result is stored in the first position of work and is returned. In this version of merge sort, there is no recursion. It does, however, introduce a large number of loops based on the number of items in the array, so it may be worth revisiting the techniques discussed in part 2 to handle the extra overhead.
The bottom line: always be on the look out for recursion in your JavaScript. Memoization and iteration are two ways to avoid excessive recursion and the long-running script dialog.
Translations
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




15 Comments
[...] Nicholas C. Zakas вÑе еще уÑкорÑет ÑваÑкрипт… пора бы уже переÑтать Speed up your JavaScript, Part 3 [...]
Linkdump #4 | CTAPbIu_MABP's BLOG on February 2nd, 2009 at 5:16 am
[...] that do too much and taught techniques such as queuing and memoization to lighten the workload. Part 3 expanded the conversation to handling recursion both with memoization and switching to an iterative [...]
Speed up your JavaScript, Part 4 | NCZOnline on February 3rd, 2009 at 7:54 pm
[...] up, he delves deeper into a generic memoizer: In part 2 of this series, I wrote briefly about handling too much recursion in a function through [...]
Ajaxian » Speeding up your JavaScript: Part 3 and 4 on February 5th, 2009 at 6:15 am
[...] Speed up your JavaScript, Part 3 | NCZOnline. [...]
My Bad Attitude » Speed up your JavaScript on February 6th, 2009 at 4:03 pm
[...] ã€åŽŸæ–‡ã€‘Speed up your JavaScript, Part 3ã€ä½œè€…】Nicholas C. [...]
如何æå‡JavaScriptçš„è¿è¡Œé€Ÿåº¦ï¼ˆé€’归篇) « 七月佑安 on March 15th, 2009 at 7:03 am
[...] memoizer example was good, check the slides for recursion [...]
JAOO 2009 - Brisbane - brettdargan.com on May 18th, 2009 at 8:33 pm
[...] ã€åŽŸæ–‡ã€‘Speed up your JavaScript, Part 3 ã€ä½œè€…】Nicholas C. Zakas ã€è¯‘文】http://cuimingda.com/2009/02/speed-up-your-javascript-part-3.html ã€è¯‘者】明达 [...]
如何æå‡JavaScriptçš„è¿è¡Œé€Ÿåº¦ï¼ˆé€’归篇) « iUE on June 3rd, 2009 at 11:32 am
[...] åœ¨è¿‡åŽ»çš„å‡ å‘¨ä¸ï¼Œæˆ‘为大家介ç»äº†å‡ ç§å¯ä»¥åŠ å¿«JavaScript脚本è¿è¡Œé€Ÿåº¦çš„æŠ€æœ¯ã€‚第一节介ç»äº†å¦‚何优化循环。第二节的é‡ç‚¹æ”¾åœ¨ä¼˜åŒ–函数内部代ç 上,还介ç»äº†é˜Ÿåˆ—(queuing)和记忆化(memoizationï¼‰ä¸¤ç§æŠ€æœ¯ï¼Œæ¥å‡è½»å‡½æ•°çš„工作负担。第三节就如何将递归转æ¢ä¸ºè¿ä»£å¾ªçŽ¯æˆ–è€…è®°å¿†åŒ–æ–¹å¼çš„è¯é¢˜ï¼Œå±•开了讨论。第四节是这个系列的最åŽä¸€ç¯‡ï¼Œä¹Ÿå°±æ˜¯æœ¬æ–‡ï¼Œå°†é‡ç‚¹é˜è¿°è¿‡å¤šçš„DOMæ“作所带æ¥çš„å½±å“。 [...]
如何æå‡JavaScriptçš„è¿è¡Œé€Ÿåº¦ï¼ˆDOM篇) « Oragg.com on June 14th, 2009 at 11:18 am
[...] 本文译自Nicholas C. Zakas于2009å¹´1月27日在个人网站上å‘表的《Speed up your JavaScript, Part 3》。原文是唯一的æ£å¼ç‰ˆï¼Œæœ¬æ–‡æ˜¯ç»è¿‡åŽŸæ–‡ä½œè€…æŽˆæƒçš„ç®€ä½“ä¸æ–‡ç¿»è¯‘版。译者在翻译的准确性上åšäº†å¤§é‡çš„åŠªåŠ›ï¼Œå¹¶æ‰¿è¯ºè¯‘æ–‡çš„å†…å®¹å®Œå…¨å¿ äºŽåŽŸæ–‡ï¼Œä½†å¯èƒ½è¿˜æ˜¯åŒ…å«ç–æ¼å’Œä¸å¦¥ä¹‹å¤„,欢迎大家指æ£ã€‚译åºå’Œè¯‘æ³¨çš„å†…å®¹æ˜¯éžæ£å¼çš„,仅代表译者个人观点。 [...]
如何æå‡JavaScriptçš„è¿è¡Œé€Ÿåº¦ï¼ˆé€’归篇) « 七月佑安 on June 14th, 2009 at 1:27 pm
[...] ã€åŽŸæ–‡ã€‘Speed up your JavaScript, Part 3 ã€ä½œè€…】Nicholas C. Zakas ã€è¯‘文】http://cuimingda.com/2009/02/speed-up-your-javascript-part-3.html ã€è¯‘者】明达 [...]
æå‡JavaScriptè¿è¡Œé€Ÿåº¦ä¹‹é€’归篇 » C&F Studio on October 12th, 2009 at 11:10 pm
Couldn’t you write the memoizer function so it creates its own cache instead of requiring one to be passed in? I think that would be similar to module technique that Crockford describes in the “Good Parts” book.
Mark Volkmann on October 25th, 2009 at 4:02 pm
@Mark – This function does that. The first line says “if cache is defined, use it, otherwise create a new object.” This makes the second argument optional.
Nicholas C. Zakas on October 25th, 2009 at 5:00 pm
[...] part 2, part 3, and part 4 of this [...]
Neil Skoglund » Blog Archive » 120 Tips, Tricks, and Tuts from 2009 Worth your Time on December 28th, 2009 at 10:21 pm
[...] http://www.nczonline.net/blog/2009/01/27/speed-up-your-javascript-part-3/ 分类: Java æ ‡ç¾: Arithmetic 评论 (0) Trackbacks (0) å‘表评论 Trackback [...]
Java Oracle Solaris—背负一身情债的ä¼é¹… » Natural Merge Sort 自然åˆå¹¶æŽ’åº on April 4th, 2010 at 4:43 am
[...] by the author on the same topic:Speed up your JavaScript – Part 1Speed up your JavaScript – Part 2Speed up your JavaScript – Part 3Speed up your JavaScript – Part 4Further Info:Google Tech Talks YouTube ChannelSpeed up your [...]
Seven Must-See Videos and Presentations for Web App Developers - Smashing Magazine on July 18th, 2010 at 3:59 am
Comments are automatically closed after 14 days.