Speed up your JavaScript, Part 2
Last week, I covered the first reason why JavaScript can take too long to execute: too much happening in a loop. There’s a similar problem with functions in that sometimes they’re just doing too much. Usually this means there’s too many loops (as opposed to too much happening in a loop), too much recursion, or simply too many different operations being performed.
Too many loops are often caused by having loops inside of loops, locking up the JavaScript engine until all iterations are complete. The most glaring example of this is the bubble sort algorithm. Though there’s no need to use this in JavaScript due to the native sort() method, it’s good to understand how it can be problematic so that you can identify similar patterns. A typical implementation of a bubble sort in JavaScript looks like this:
function bubbleSort(items){
for (var i=items.length-1; i >= 0; i--){
for (var j=items.length-i; j >= 0; j--){
if (items[j] < items[j-1]){
var temp = items[j];
items[j] = items[j-1];
items[j-1] = temp;
}
}
}
}
Thinking back to your computer science days, you’ll probably remember that bubble sort is one of the least efficient sorting algorithms. The problem is for every n items in the array, there must be n2 loop iterations. This processing can take forever if there’s a large amount of array items. The comparison and swap operation done during the inner loop is actually quite simple, it’s just the number of times that it’s repeated in sequence that causes the problem. This can cause the browser to grind to a halt and, potentially, result in the long-running script dialog.
A couple years ago, fellow Yahoo Julien Lecomte wrote a post entitled,
Running CPU Intensive JavaScript Computations in a Web Browser, in which he described how to break up large JavaScript operations into several parts. One of his clearest examples was refactoring a bubble sort into multiple steps, each of which executes a single trip through the array. I’ve augmented his code somewhat, but the approach remains the same:
function bubbleSort(array, onComplete){
var pos = 0;
(function(){
var j, value;
for (j=array.length; j > pos; j--){
if (array[j] < array[j-1]){
value = data[j];
data[j] = data[j-1];
data[j-1] = value;
}
}
pos++;
if (pos < array.length){
setTimeout(arguments.callee,10);
} else {
onComplete();
}
})();
}
This function performs a bubble sort in an asynchronous manner, stopping after each trip through the array before continuing on to the next leg. The onComplete() function is called when the array is completely sorted as notification that the data is ready. The bubbleSort() function uses the same basic technique as the chunk() function presented in my last post: use an anonymous function to wrap the behavior and then pass arguments.callee into setTimeout() to repeat the process until complete. This function is a good example of how you can split up embedded loops into a series of steps to free up the browser.
A similar problem is too much recursion. Each additional recursive call takes up memory, and eventually will slow down the browser. The annoying thing is that you may reach a memory limit before the long-running script dialog pops up and leave the browser in an unusable state. Crockford had a good discussion about this in his latest talk. The example he uses is a function that generates a Fibonacci sequence:
function fibonacci (n) {
return n < 2 ? n :
fibonacci(n - 1) +
fibonacci(n - 2);
};
As Crockford points out, a call to fibonacci(40) results in 331,160,280 calls to itself. The solution to avoid too much recursion is to use memoization, a technique for caching previously-calculated values. Crockford introduces the following memoization function that can be used to create memoized versions of functions dealing with numbers:
function memoizer(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;
};
He then applies this to the Fibonacci sequence generator:
var fibonacci =
memoizer([0, 1], function (recur, n) {
return recur(n - 1) + recur(n - 2);
});
Calling fibonacci(40) using this code results in only 40 calls to the function, a vast improvement over the original. The overall lesson from memoization is that you should never calculate the same result twice; if there’s a value you’ll need more than once, store it for later use rather than running the code to generate it again.
The final thing that causes functions to execute slowly is, as mentioned previously, that it’s just doing too much. Usually it’s because of a pattern such as this:
function doAlot(){
doSomething();
doSomethingElse();
doOneMoreThing();
}
Here, there’s three clearly distinct pieces of code that are being executed. The important thing to notice is that none of the functions rely on the other functions to complete their task; they are essentially independent of one another and just need to happen in sequence at a given point in time. In situations like this, you can use a variant of the chunk() method to execute a series of functions in a row without holding up the browser:
function schedule(functions, context){
setTimeout(function(){
var process = functions.shift();
process.call(context);
if (functions.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
The schedule function accepts two arguments, an array of functions to execute and a context object indicating the value of this inside of each function. The functions array acts as a queue, with the topmost function being removed and executed each time the timer is executed. This function can be used to execute a series of functions in a row like this:
schedule([doSomething, doSomethingElse, doOneMoreThing], window);
I’m expecting that JavaScript libraries will soon start including more processing functions such as this. YUI has already added the Queue object in version 3.0 that helps to manage the running of several functions in a row using a timer.
Regardless of the tools available to help split up complex processes, it’s still vital for developers to be able to understand and identify bottlenecks that will benefit from using this approach. Whether there be too many loops, too much recursion, or just plain too much going on, you now know how to deal with each. Remember, the techniques and functions presented here are just a starting point and not a golden bullet, you should (and will likely have to) modify the code presented so that it works for your specific usage.
Update (1/20): Fixed copy/paste error in schedule() function.
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.




26 Comments
Hey Nicholas,
You’re code has a flaw. You are using the call method on “process” instead of “item”:
function schedule(functions, context){
setTimeout(function(){
var item = functions.shift();
// Not process, but item…
process.call(context);
if (functions.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
Change that and you’re good to go!
Cheers.
Joe McCann
Joe McCann on January 20th, 2009 at 5:51 pm
Hey Joe, thanks for the heads up! I’ve fixed the code.
Nicholas C. Zakas on January 20th, 2009 at 7:15 pm
No worries! Read your first book and am looking forward to this edition as well.
Cheers.
Joe
Joseph McCann on January 22nd, 2009 at 12:15 am
[...] C. Zakas is back at it with part two of his Speed up your JavaScript [...]
Ajaxian » Speed up your JavaScript with memoization and queueing on January 23rd, 2009 at 7:04 am
Thanks for the information
Timothy on January 23rd, 2009 at 12:29 pm
Speed up your JavaScript, Part 2…
Thank you for submitting this cool story – Trackback from DotNetShoutout…
DotNetShoutout on January 24th, 2009 at 12:38 am
Good post. My data structures professor was one of the biggest pricks I’ve ever met but he did say something that I never forgot: “If your program is too slow then its time to pick a better algorithm!” So I would say that it would make more sense to replace a bubble sort with a merge sort if a large amount of data needs to be sorted in a JavaScript or any other program.
Topnotch on January 24th, 2009 at 7:56 am
Hi Nicholas
I like your article – very informative. I tried creating a more generic method for memoization. Maybe you can make some use of it – see http://www.jslab.dk/library/Function.memoize for more info.
Dok on January 24th, 2009 at 11:28 am
[...] Speed up your JavaScript, Part 2 [...]
Interesting Finds: 2009.01.24 | Web Hosting and Domains on January 24th, 2009 at 12:42 pm
[...] Nicholas C. Zakas Ñнова уÑкорÑет JavaScript [...]
Linkdump #3 | CTAPbIu_MABP's BLOG on January 25th, 2009 at 2:50 pm
[...] 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 [...]
Speed up your JavaScript, Part 3 | NCZOnline on January 27th, 2009 at 9:01 am
[...] Speed up your JavaScript, Part 2 NCZOnline (tags: javascript) [...]
links for 2009-01-29 « pabloidz on January 29th, 2009 at 8:01 am
YUI 3 Queue util is based on work done for YUI 2 DataTable in which it is also a standalone util named Chain.
http://developer.yahoo.com/yui/docs/YAHOO.util.Chain.html
Unlike Queue, it is not offered a la carte, though the sources are available at http://github.com/yui.
Luke on January 29th, 2009 at 12:32 pm
[...] for speeding up your JavaScript. Part 1 covered how to deal with loops that are doing too much. Part 2 focused on functions that do too much and taught techniques such as queuing and memoization to [...]
Speed up your JavaScript, Part 4 | NCZOnline on February 3rd, 2009 at 1:20 pm
[...] Speed up your JavaScript, Part 2 | NCZOnline. [...]
My Bad Attitude » Speed up your JavaScript on February 5th, 2009 at 12:25 pm
[...] ã€åŽŸæ–‡æ ‡é¢˜ã€‘Speed up your JavaScript, Part 2ã€åŽŸæ–‡ä½œè€…ã€‘Nicholas C. [...]
如何æå‡JavaScriptçš„è¿è¡Œé€Ÿåº¦ï¼ˆå‡½æ•°ç¯‡ï¼‰ « 七月佑安 on March 15th, 2009 at 7:08 am
[...] C. Zakas presents, among other things, an interesting way of scheduling work to be done in batches, so the browser doesn’t become unresponsive for the [...]
queuing for batch scheduling - Jason's postings and stuff on May 3rd, 2009 at 7:08 pm
[...] ã€åŽŸæ–‡æ ‡é¢˜ã€‘Speed up your JavaScript, Part 2 ã€åŽŸæ–‡ä½œè€…ã€‘Nicholas C. [...]
如何æå‡JavaScriptçš„è¿è¡Œé€Ÿåº¦ï¼ˆå‡½æ•°ç¯‡ï¼‰ « iUE on June 3rd, 2009 at 11:33 am
[...] Speed up your Javascript part 2 [...]
Speed up your Javascript - Code Couch on June 6th, 2009 at 12:52 pm
[...] come and share some tips along those lines to the folks at Google. In following along my series of blog posts about JavaScript performance, I entitled this talk, Speed up your [...]
Speed up your JavaScript: The talk | NCZOnline on June 8th, 2009 at 11:12 pm
[...] åœ¨è¿‡åŽ»çš„å‡ å‘¨ä¸ï¼Œæˆ‘为大家介ç»äº†å‡ ç§å¯ä»¥åŠ å¿«JavaScript脚本è¿è¡Œé€Ÿåº¦çš„æŠ€æœ¯ã€‚第一节介ç»äº†å¦‚何优化循环。第二节的é‡ç‚¹æ”¾åœ¨ä¼˜åŒ–函数内部代ç 上,还介ç»äº†é˜Ÿåˆ—(queuing)和记忆化(memoizationï¼‰ä¸¤ç§æŠ€æœ¯ï¼Œæ¥å‡è½»å‡½æ•°çš„工作负担。第三节就如何将递归转æ¢ä¸ºè¿ä»£å¾ªçŽ¯æˆ–è€…è®°å¿†åŒ–æ–¹å¼çš„è¯é¢˜ï¼Œå±•开了讨论。第四节是这个系列的最åŽä¸€ç¯‡ï¼Œä¹Ÿå°±æ˜¯æœ¬æ–‡ï¼Œå°†é‡ç‚¹é˜è¿°è¿‡å¤šçš„DOMæ“作所带æ¥çš„å½±å“。 [...]
如何æå‡JavaScriptçš„è¿è¡Œé€Ÿåº¦ï¼ˆDOM篇) « Oragg.com on June 14th, 2009 at 11:16 am
[...] 本文译自Nicholas C. Zakas于2009å¹´1月20日在个人网站上å‘表的《Speed up your JavaScript, Part 2》。原文是唯一的æ£å¼ç‰ˆï¼Œæœ¬æ–‡æ˜¯ç»è¿‡åŽŸæ–‡ä½œè€…æŽˆæƒçš„ç®€ä½“ä¸æ–‡ç¿»è¯‘版。译者在翻译的准确性上åšäº†å¤§é‡çš„åŠªåŠ›ï¼Œå¹¶æ‰¿è¯ºè¯‘æ–‡çš„å†…å®¹å®Œå…¨å¿ äºŽåŽŸæ–‡ï¼Œä½†å¯èƒ½è¿˜æ˜¯åŒ…å«ç–æ¼å’Œä¸å¦¥ä¹‹å¤„,欢迎大家指æ£ã€‚译åºå’Œè¯‘æ³¨çš„å†…å®¹æ˜¯éžæ£å¼çš„,仅代表译者个人观点。 [...]
七月佑安 on June 14th, 2009 at 1:39 pm
[...] 递归是拖慢脚本è¿è¡Œé€Ÿåº¦çš„大敌之一。太多的递归会让æµè§ˆå™¨å˜å¾—è¶Šæ¥è¶Šæ…¢ç›´åˆ°æ»æŽ‰æˆ–者莫å其妙的çªç„¶è‡ªåŠ¨é€€å‡ºï¼Œæ‰€ä»¥æˆ‘ä»¬ä¸€å®šè¦è§£å†³åœ¨JavaScriptä¸å‡ºçŽ°çš„è¿™ä¸€ç³»åˆ—æ€§èƒ½é—®é¢˜ã€‚åœ¨è¿™ä¸ªç³»åˆ—æ–‡ç« çš„ç¬¬äºŒç¯‡ä¸ï¼Œæˆ‘曾ç»ç®€çŸçš„介ç»äº†å¦‚何通过memoizationæŠ€æœ¯æ¥æ›¿ä»£å‡½æ•°ä¸å¤ªå¤šçš„递归调用。memoization是一ç§å¯ä»¥ç¼“å˜ä¹‹å‰è¿ç®—ç»“æžœçš„æŠ€æœ¯ï¼Œè¿™æ ·æˆ‘ä»¬å°±ä¸éœ€è¦é‡æ–°è®¡ç®—那些已ç»è®¡ç®—过的结果。对于通过递归æ¥è¿›è¡Œè®¡ç®—的函数,memoization简直是太有用了。我现在使用的memoizer是由 Crockford写的,主è¦åº”用在那些返回整数的递归è¿ç®—ä¸ã€‚å½“ç„¶å¹¶ä¸æ˜¯æ‰€æœ‰çš„递归函数都返回整数,所以我们需è¦ä¸€ä¸ªæ›´åŠ é€šç”¨çš„memoizer()函数æ¥å¤„ç†æ›´å¤šç±»åž‹çš„递归函数。 [...]
如何æå‡JavaScriptçš„è¿è¡Œé€Ÿåº¦ï¼ˆé€’归篇) « 七月佑安 on June 14th, 2009 at 2:29 pm
[...] lot of research on performance, resulting in the Speed Up Your JavaScript blog post series (part 1, part 2, part 3, part 4) as well as several talks, namely JavaScript Variable Performance at the San [...]
Announcing High Performance JavaScript | NCZOnline on February 9th, 2010 at 9:02 am
Whoa, thank you very much this is an awesome article ( yes I know it was submitted over a year ago ). Thanks, I’m going to try out these ( part 1 as well ) on my own code to hopefully improve it
James on February 26th, 2010 at 2:22 am
[...] be done by you.”Series of articles 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 [...]
Seven Must-See Videos and Presentations for Web App Developers - Smashing Magazine on July 18th, 2010 at 3:58 am
Comments are automatically closed after 14 days.