jQuery, caching and performance across browsers

Intro

Here at Sunview we create a lot of web applications for people.  We are always looking to give the end user a great experience.  This means that the user interface needs to be snappy and responsive.

jQuery is used extensively to create user interfaces for our clients.  Also creating custom user interfaces happens quite a bit, since every client needs something unique.  This means we are writing lots of code that does some heavy DOM manipulation.  Making sure that we do this intelligently is important.

Right to the Test

If you want to skip all the boring explanation and jump right into running the test and viewing the code, go right ahead.  You can by going to http://sunviewconsulting.ca/blog/2011_05_25/index.html.  Be careful, since this is essentially a load test, this may cause your browser to become un-repsonsive.

Caching elements with jQuery

jQuery is all about making DOM access and manipulation easy.  We can select something by id.

<div id='selectme'>Some HTML</div>
$('#selectme').html('New HTML');

Or we can select something by CSS class

<div class='selectme'>Some HTML</div>
$('div.selectme').html('New HTML');

If you are a web dev, then you should already know that selecting by ID with jQuery will always be much faster than selecting by class.  The reason is that selecting by ID will use the browsers native getElementByID method.   This is great, except we won’t always be able to select things by ID for a few reasons.

  • Each ID must be unique within an HTML document
  • We would need to know the ID before hand (for dynamically generated stuff this may not be possible).
  • Maybe we want to select more than one element at a time.

So if we are selecting a HTML DOM element using jQuery, it could be an expensive operation.  We want to minimize this cost.  Lets look at this jQuery/JavaScript snippet.

for (var i=0; i<10; i++)
{
   $('ul.menu').append('<li>' + i + '</li>');
}

Straight forward loop.  It finds the <ul> element with a class of menu, and appends a list item to it.  This works great.

However, we are using jQuery to lookup the <ul> element on each iteration.  This could be a hugely expensive operation if we have thousands of elements within the DOM.  Don’t get me wrong, jQuery and it’s Sizzle engine is optimized and frankly bad-ass, but at the end of the day it’s still doing some heavy lifting.  Help it out by caching that <ul> element rather than looking it up each time.

var ul = $('ul.menu');
for (var i=0; i<10; i++)
{
   ul.append('<li>' + i + '</li>');
}

Ahhh, much better.  Now we find the <ul> element once, store it in a local variable, and then go through the loop.  Instead of 10 queries, jQuery has done 1.  We’ve got the same results but a happier web browser and a happier end user.

Big String or Lots of Element Insertion?

So there is potentially another optimization we could do to that above loop.  We are adding an element to the HTML DOM on each iteration.  Now since JavaScript isn’t threaded, we shouldn’t be causing any UI updates to occur, but we may be causing a re-org of the browsers internal HTML DOM tree on each insert.  We could instead generate a large HTML fragment string and insert all at once at the end of the loop.

var html = '';
for (var i=0; i<10; i++)
{
   html += '<li>' + i + '</li>';
}
$('ul.menu').append(html);

Now I’m not sure if this will be faster than the above method, because jQuery will be using different internal implementation behind the scenes depending on what browser you are using.  So we will result to good ol’ un-scientific test to determine the best course of action.

The Tests

The test page contains five different tests using combinations of the above methods.  Each test has the same effect on the DOM.  We are going to dynamically generate 100 <ul> elements and within each of those <ul> elements we are going to append 100 <li> elements.  Maybe not something you would normally do in real life, but fits the purpose of this blog post well enough.

By Class – No Cache

This is the most naive approach.  Access each <ul> tag by it’s class name, and don’t even bother caching it while adding it’s <li> children.

By Class – Cache

Must better!  Access each <ul> tag by it’s class name, but this time, cache it for the inner loop to add children.

By ID – No Cache

We know this will be fast, but maybe you won’t always be able to do this?  Either way, access each <ul> tag by ID and don’t bother caching it while adding it’s <li> children.

By ID – Cache

Best of both worlds.  Access that <ul> by ID and cache it while adding children.

By String

The potentially whacky method of generating a large HTML fragment and adding it all at once to the DOM.  Doesn’t really matter if we access the <ul> by ID or by class to demonstrate this.

The Testing Rig

To different browsers we are using a MacBook Pro

  • MAC OS X 10.6.4
  • 2.8 GHz Intel Core 2 Duo
  • 8 GB 1066 DDR3

And while we are at it we are also testing some mobile devices

  • iPhone 4 (iOS 4.2)
  • BlackBerry Bold 9780 v6.0
  • iOS Simulator (iOS 4.2)

The browsers we are testing are

  • Chrome 11
  • Firefox 5.0
  • Safari 5.0.1
  • IE 9.0
  • IE 8.0
  • IE 7.0
  • IE 6.0

The Results

All browsers.  10,000 elements added.  Vertical axis is time (ms).

IE removed (for being too slow).  10,000 elements added. Vertical axis is time (ms).

Mobile browsers.  625 elements added.  Vertical axis is time (ms).

The Conclusion.

As we expected, querying for elements by ID is the fastest across the board.  However, using some intelligent caching you can use class selectors and get similar performance.

Using the large HTML string fragment also worked very well, almost winning across the board.  The only exception is FireFox 5.  If anyone knows the technical reasons why FireFox 5′s internal implementation of this is slower I would love to know why.

The mobile browsers also did very well (for a much smaller dataset of course). We see the BlackBerry browser holding well against the iPhone in terms of performance.  (Which is not surprising since they are both webkit based).

About clint

Clint has been active in the Toronto IT landscape for over 10 years. After first working with a local IT consulting firm, Clint left in 2005 to start the interactive company HotdogTree INC. Clint delivered projects for the largest advertising agencies, Fortune 500 companies and government agencies. Focused on delivering successful projects for his clients, Clint has now taken the reigns of Sunview's internal product development shop. Clint has a MSc. Computing and Information Sciences from Queens' University, Kingston Ontario. He currently is researching and developing new methods of rapid application development focused on mobile and ubiquitous access.
This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

9 Responses to jQuery, caching and performance across browsers

  1. Osman Zeki says:

    Great article. These are obvious optimization tricks that I should put in practice more often. Especially since Javascript can be so demanding on older hardware/browsers.

  2. I would like to see some more numbers for mobile browsers. It’s getting to the point where all new desktop browsers are pretty fast but I feel there would be a lot of fragmentation on mobile.

  3. Gollem Pickering says:

    Why don’t you do some real software engineering, forgot about web toys.

  4. Pingback: 網站製作學習誌 » [Web] 連結分享

  5. Shezard says:

    hum, i guess firefox implementation of the += is slow, maybe a test using

    var html = [];
    for (var i=0; i<10; i++)
    {
    html.push('' + i + '');
    }
    $('ul.menu').append(html.join(''));

    would have been faster

  6. clint says:

    Thanks for the post! Yes, you are absolutely right. When people think DRY then tend to just think about literally repeating code. DRY also applies to the effect that code has. You are correct in that this isn’t really caching. But it was the sexiest description I could think of.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>