Wednesday, February 16, 2011

jQuery: remove() Performance Issues Tested

I was reading the comments on the jquery .remove() function, and came across this:
If you've got a container with a lot of child elements you had better empty the container element before removing it. Due to the way jQuery handles remove vs empty, empty is thousands of times faster, at least in this situation.

So do this:
$('#container').empty().remove();

…instead of this:
$('#container').remove();
So for each time I've used remove() since then, I've been doing empty().remove(), as it is "thousands of times faster".  Well, I've been meaning to test this so here we go.  I created a simple template for testing.  The idea was to created a table with 5000 rows with elements nested 3 levels deep.  As expected, jQuery drops these things from the DOM pretty quickly.  Here is the template (I'm looping using ColdFusion, but you could recreate this in any other language:

<script type="text/javascript" src="jquery-1.4.2.min.js"></script>
<script type="text/javascript">
  function go (){
    var start = new Date();
    $('#tbltest').empty().remove();
    var end = new Date();
    alert(end - start);
  }
</script>
<button onclick="go();">Remove</button>
<table id="tbltest">
  <tbody>
    <cfloop from="1" to="5000" index="i">
      <tr>
        <td><a href="javascript:void(0);"><span>Text</span></a></td>
        <td><a href="javascript:void(0);"><span>Text</span></a></td>
        <td><a href="javascript:void(0);"><span>Text</span></a></td>
        <td><a href="javascript:void(0);"><span>Text</span></a></td>
        <td><a href="javascript:void(0);"><span>Text</span></a></td>
        <td><a href="javascript:void(0);"><span>Text</span></a></td>
        <td><a href="javascript:void(0);"><span>Text</span></a></td>
        <td><a href="javascript:void(0);"><span>Text</span></a></td>
      </tr>
    </cfloop>
  </tbody>
</table>

Note that I'm using jQuery 1.4.2.  The plan was to allow the page to load fully and then trigger the remove() or empty().remove().

First, when running this 10 times using remove():
  1. 869ms
  2. 906ms
  3. 907ms
  4. 919ms
  5. 889ms
  6. 912ms
  7. 948ms
  8. 1055ms
  9. 933ms
  10. 900ms
Then, 10 times using empty().remove():
  1. 941ms
  2. 897ms
  3. 881ms
  4. 923ms
  5. 954ms
  6. 951ms
  7. 1015ms
  8. 968ms
  9. 939ms
  10. 962ms
When averaged the remove() method took 924ms and empty().remove() averaged 943ms.  Not the results I was expecting to see, but I'm not ready to call this busted just yet. I had a hunch that the issue was with deeply nested objects and the way that jQuery traverses the DOM.  I changed the code to have several layers of nested tables as such:

<script type="text/javascript" src="jquery-1.4.2.min.js"></script>
<script type="text/javascript">
  function go (){
    var start = new Date();
    $('#tbltest').empty().remove();
    var end = new Date();
    alert(end - start);
  }
</script>
<button onclick="go();">Remove</button>
<table id="tbltest">
  <tbody>
    <cfloop from="1" to="2000" index="i">
      <tr>
        <td>
          <table>
          <tbody>
            <tr>
              <td>
                <table>
                <tbody>
                  <tr>
                    <td>
                      <table>
                      <tbody>
                        <tr>
                          <td>
                          <span><span><span><span><span><span><a href="javascript:void(0);"><span>Text</span></a></span></span></span></span></span></span>
                          </td>
                          <td>
                          <span><span><span><span><span><span><a href="javascript:void(0);"><span>Text</span></a></span></span></span></span></span></span>
                          </td>
                          <td>
                          <span><span><span><span><span><span><a href="javascript:void(0);"><span>Text</span></a></span></span></span></span></span></span>
                          </td>
                          <td>
                          <span><span><span><span><span><span><a href="javascript:void(0);"><span>Text</span></a></span></span></span></span></span></span>
                          </td>
                          <td>
                          <span><span><span><span><span><span><a href="javascript:void(0);"><span>Text</span></a></span></span></span></span></span></span>
                          </td>
                        </tr>
                      </tbody>
                      </table>
                    </td>
                  </tr>
                </tbody>
                </table>
              </td>
            </tr>
          </tbody>
          </table>
        </td>
      </tr>
      </tr>
    </cfloop>
  </tbody>
</table>

remove():
  1. 769ms
  2. 812ms
  3. 767ms
  4. 744ms
  5. 822ms
  6. 778ms
  7. 766ms
  8. 822ms
  9. 1018ms
  10. 778ms
empty().remove():
  1. 997ms
  2. 782ms
  3. 792ms
  4. 978ms
  5. 1062ms
  6. 765ms
  7. 728ms
  8. 759ms
  9. 1003ms
  10. 834ms
Averaged, remove() took 808ms and empty().remove() took 772ms.  This result made sense and was more the expectation I expected to see.  Perhaps over a large set of elements it would actually matter.  I'd say it isn't worth worrying about, but at least my curiosity has been quelled.

A few things to call out here are that I am using jQuery 1.4.2 on Firefox 3.6.13.  This matters as different results could be given in IE with it's JS engine, as well as the comment on the jQuery remove() page may have been referring to another version, although I did choose 1.4.2 to mimic the date of the post.

    2 comments:

    1. Nicholas:

      My guess is the person who made the comment was using jQuery 1.2.x. I seem to recall similar issues w/older versions of jQuery. The problem w/his comment is he doesn't show an example or even declare the version of jQuery he's using.

      ReplyDelete
    2. Looks to be dramatic for IE. http://jsperf.com/removing-large-tables

      ReplyDelete