Hi-performance Javascript Tips #3: Less is More [Updated 2009-04-09]

This should really be Tip #1 since it’s the most critical of all. Let me just say this as clearly as possible:

Your fastest Javascript projects are the ones that have the least Javascript!

Sure, Javascript engines have matured. Safari’s Nitro, Chrome’s V8, Firefox’s TraceMonkey, and Opera’s Carackan all kick some serious butt. (IE8’s JScript, unfortunately, still sucks wind.) However, routines written in C++ still run orders of magnitude faster in most cases.

Don’t write Javascript to do something your browser already does at compiled-code speeds.

Do you have any idea now many person-years of effort went into the regular expressions engine in your browser? Tap into that resource!

Here are some other resources you may be under-utilizing:

  • Array.prototype.join (example)
  • Array.prototype.slice (example)
  • RegExp (many string-related optimizations!)
  • Array iterators
  • DOM selectors (such as Acme (dojo) and Sizzle)

I’ll dive into each of these in later blog posts, but here’s a teaser. This example shows how to replace a relatively slow while loop with an Array iterator.

Our while loop:

var nodes = dojo.query('*[title]'), // using dojo just as an example
    found, // we're looking for a particular node and will store it here
    len = nodes.length, // performance boost to grab this outside of the loop
    i = 0; // just an iteration counter
 
while (!found && i < len) {
    // found will remain false until we find our node
    found = nodes[i].getAttribute('title').indexOf('Loading') >= 0 && nodes[i];
    i++;
}
if (found) {
    // do something with <<found>> here
}

That’s not a bad while loop. You’ll notice that I am overloading the meaning of the found variable. This is an acceptable practice in high-performance situations as long as the code is well-documented to prevent confusion.

Here’s the same loop utilizing the Array iterator some wrapped by dojo NodeList’s some method:

var found; // we're looking for a particular node and will store it here
dojo.query('*[title]').some(function (node) {
    // found will remain false until we find our node
    found = node.getAttribute('title').indexOf('Loading') >= 0 && node;
    return found;
});
if (found) {
    // do something with <<found>> here
}

Leaner! Faster! Sexier!


OK. Here’s the point where I scratch my head. I just ran both of these against dojocampus.org in FF 3.1 and Safari 4.0. To my surprise, the while loop performed slightly better than the some (1ms difference). I removed the [title] attribute from the query selector to get some better test data. Same story.

There was a big difference in IE7, though.

I guess the latest Javascript engines really do kick butt!!!!


Update 2009-04-09:

My best guess is that the NodeList that dojo.query returns is slowing our example code. A NodeList is not a true array. It is an Array-like object, which simply means it exposes its list of items as properties whose names are Cardinal number and also exposes a length property. So I created a quick and dirty test on a true array:

// using some
var a = [];
for(var i = 0; i < 100000; i++) { a[i] = new Date(2009, 1, i); };
var found, s = new Date();
a.some(function (item, i) {
    found = i == 99999 ? item : false;
    return found;
});
console.log((new Date()) - s, found);
 
// using while
var len = a.length, found = false, s = new Date(), i = 0;
while (!found && i < len) {
   found = i == 99999 ? a[i] : false;
    i++;
}
console.log((new Date()) - s, found);

The results showed that some ran about 17% faster in Firefox 3.0, 32% faster in Firefox 3.1beta, and 43% faster in Safari 4.0beta. Of course, the code inside our loop is very simple. More complex code will consume a larger slice of the total time and will therefore make the effect of the loop mechanism less important.

Moral of the story: to get the best performance when looping through real arrays (rather than Nodelists or Array-like objects), use the Array iterators or their browser-safe counterparts in the major libraries. When using NodeLists, either method works great as long as you’ve tuned your loop correctly. (Sounds like another blog post…)

I’d like to hear from you! What library do you use and how does it handle Array iterators?