Performance is in the Details

This past week, some colleagues and I sat around a brainstorming table trying to isolate what was causing a particular page to load incredibly slowly in Internet Explorer 6. Drawing on our combined years of experience, and our mutual disdain for IE6, we came up with what we believed to be a solid list of potential bottlenecks that included (among other things):

  1. DOM Parsing (the DOM is very large for this particular page)
  2. Slow / Inefficient Ajax requests
  3. Parsing large JSON strings returned by Ajax request
  4. Too many event handler assignments

We tested each of these theories among the 5-6 others that we had collected and could not find any particular area that was acting particularly slow. Unfortunately, the lack of any serious profiling tools for IE made the task of doing real benchmarks almost impossible. We scratched our heads and adjourned for our 3-day weekend.

In the process of fixing random software defects, one of our other lead engineers stumbled across this line of code that one of our more junior engineers had checked in:

for(var i=0; i < $$('div.contents').length; i++)

My head was spinning just looking at that one statement. For those unfamiliar with Prototype, the $$ method is a sometimes useful, but horribly inefficient means of searching the DOM. In plain English, this method will walk the entire DOM of your document, and check if it matches the CSS selector contained within, and add a reference to that element to an array if it does. When the traversal is complete, the array is returned. Do you see the problem now? Each time the loop ran (conceivably 50+ times in this case, however many elements matched div.contents) it would have to do an entire DOM traversal. Additionally, it would have to calculate the length of the returned array each time!

For reference, as a service to all of the other engineers-those who don't yet know as well as those who have had to clean up after someone who didn't know, better practice would be:

var contents = $$('div.contents');
var length = contents.length;
for(var i=0; i < length; i++)

However, it would be far more advisable to restrict your search of the DOM using prototype's Element.select method to only check the portions of the DOM.

The important take-away here is that you need to exercise extreme caution when using any method that traverses the DOM; especially as long as IE6 is a large player in the browser market as its DOM traversal is notoriously slow. Prototype's $$ is an especially dangerous method (even when used properly) and should only be employed in cases when it truly makes sense—that is, when you need to check the entirety of the document. Far more often, you will know a containing element from which to base your search:

var oddRows = $("container").select("li.odd");

Finally, always remember to calculate all of your parameters outside of a for statement (as in example 2 above). Doing so as part of the statement frankly reeks of the kind of amateur development practice one might get from reading a collection of some of the worst technical books on the market as for some reason they all seem to advocate this kind of thing.

DOM Traversal is expensive. Know what you're looking for and determine the best way to find it; not the easiest.  

  • http://www.ericdelabar.com/2008/04/javascript-getelementbyid-performance.html Eric DeLabar

    At my employer we’re dealing with some similar performance problems, as much as I love prototype, in the wrong hands its a very dangerous tool. I suppose it’s a case of learning by scraping you fingers with a hand tool before you cut them off entirely with a power tool.

    If you’re interested, my link goes to a post I wrote on my website talking about a similar performance problem with the ‘$’ function. It also includes some performance metrics on a few different browsers.

  • http://briancrescimanno.com Brian

    Very interesting post on your site, Eric. You are right in your assessment that the short-hand nature of the $() method can encourage poor practices. Thanks for the feedback!

  • Brian

    $$ has been heavily optimized since it first appeared in the Prototype framework so I wouldn’t be too afraid of using it; however, it does not make up for this code. Tell me who did this so I can smack them. Also, to improve performance, remove the life of a variable (letting garbage collection remove it sooner too) do this:

    for(var i=0, len = $$(‘div.contents’).length; i<len; i ){ /** .. */ }

    Sorry for trolling your blog, I got bored and saw the link from your LinkedIn profile. :)

  • http://www.linkedin.com/in/neilgreen Neil

    Ah, Little Johnny Toggler and his wonderful for loop.

    The overaching problem (IMHO) isn’t just DOM traversal, but a gross lack of understanding of the way IE6 (and to a lesser degree IE7) looks up pointer references in JavaScript.

    Man, we need team blog/discussion forum.

  • http://briancrescimanno.com Brian

    Here’s what’s interesting (in response to to Antonelli’s post above):

    My (admittedly non-scientific) testing has shown that IE6 will still lose its lunch over this loop structure:

    for(var i=0, len = $$(’div.contents’).length; i

    Yes, it *should* work as the value of the length should be cached–but everything that I’ve seen from what IE6′s JScript parser is that is NOT the case.

  • http://diyism.com diyism

    Why not use “jQuery Live Bind”:

    $.fn.is_match=function(selector)
    {selector=selector.split(/\s /).reverse().join(‘ ‘)
    .split(‘ > ‘).join(‘”).parent(“‘)
    .split(‘ ‘).join(‘”).prev(“‘)
    .split(‘ ~ ‘).join(‘”).prevAll(“‘)
    .split(/\s /).join(‘”).parents(“‘);
    return eval(‘this.filter(“‘ selector ‘”).length’);
    }
    $.live_bind=function(selector, etype, fn)
    {$(document).bind(etype,
    function(event)
    {var event=event || window.event,
    src_ele=event.srcElement || event.target;
    if ($(src_ele).is_match(selector))
    {fn();
    }
    }
    );
    }

    hello2
    hello3
    hello1
    hello

    hello2
    hello3
    hello1

    function alr(){alert(‘helloooo’);}
    $.live_bind(“.kkkk .ppp1 .ppp[title='333']“, ‘click’, alr);
    $(‘.kkkk’).append(‘hello’);

  • http://www.chaneloutletstores.com chanel

    Thanks a landlord it! I acquired yet some insight. Life is so colorful, we should be able to live in, such as Korea and honor the planet. Human life is like rivers, slowly flowing, flowing rivers, flowing through the snow, flows through the prairie and ultimately into the sea, return to the embrace of nature, start a new reincarnation. Allow us to feel the meaning of life will come only to those you have those memories http://www.cheap-nikeshox.com