Looping Out

Yes, I confess, I follow @DHH on Twitter. He can be rather opinionated, but he has an interesting point of view on things. Plus, it is a good way to keep up on the thinking behind Ruby on Rails. Just this evening he put a shot across the bow of JQuery with this tweet:

Since JavaScript library performance is something that interests me, I took a look at the benchmark he linked to. It is a pretty straightforward test of JQuery’s each() method compared to a custom function called quickEach(). On my MBP, the results look like this:

First, kudos to jamespadolsey for coming up with this. The speed difference is pretty shocking. But even more interesting, the test harness is extensible, so I decided to see how the latest Ext-Core stacks up.

First, I had to tweak the setup code a bit. It was creating a JQuery object which wrapped an array:

var a = $('
').append(Array(100).join('')).find('a');

I kept it simple and turned this in to:

var a = $('
').append(Array(100).join('')).find('a'); var b = a.toArray();

This gave me a real JavaScript array to work with. Then, I added a new test case which looked this:

Ext.each(b, function(item) {
    Ext.get(item); // Ext.Element
});

Running the jsPerf test with this snippet gave these results:

My first reaction to this was along the lines of WTF! My faithful Ext-Core was getting its ass handed to it by JQuery. But then I spotted the error in my ways. Using Ext.get() is very expensive for operations where you plan on discarding the Ext.Element afterwards. It adds an id to the DOM element, then puts the wrapped DOM element into a cache to speed up subsequent operations. For cases like this, where we’re going to throw the Ext.Element away, the Ext.fly() method is what should be used.

Here are the results using Ext.fly() instead:

Now we’re talking! Using Ext.fly() gave some pretty good results, although there are probably some ideas to be borrowed from the hand-rolled JQuery quickEach() method.

Not being satisfied with a contrived example which was just discarding the wrapped DOM element, I then tweaked the test. I changed each of the test cases to add a class to the wrapped element inside the loops. For giggles, I added two additional tests. One using JQuery’s addClass() method against the set of elements, and one doing the same thing using Ext.select().

Here are the results:

Suddenly that speed difference in the first test cases starts to look a lot less significant when it actually comes time to do something in the loop. Adding the class to the DOM node wrapped in an Ext.Element using Ext-Core turns the first results upside-down.

Even more interesting was JQuery’s performance using addClass() on the JQuery element and not using the each iterator. It looks like JQuery is soundly implemented for set operations. So the lesson is treat JQuery more like SQL than code — if you’re trying to do each(), you might be thinking about the problem wrong.

I won’t even attempt to address the whole “beauty” angle. I’ve learned from the ExtJS-JQuery flame wars that beauty is definitely in the eye of the beholder. And if you want to dork with this yourselves, my playground is Revision 15.

2 thoughts on “Looping Out”

  1. This is a good article and a good look at the jsperf tests. But, I think the whole premise of the test is a bit skewed. The point is not that jQuery.each() is slower than jQuery.quickEach(), although that’s a secondary outcome.

    The real point is that $(x) wrapping, where `x` is some object, like `this`, is SLOW… DOG SLOW… if all you need is to treat the object as a single element collection so you can get the helper jquery collection methods.

    There’s a much faster way to initialize a single element collection than $(x):

    var $this = $(0); // outside the loop somewhere
    // now, inside the loop
    $this.context = $this[0] = this;

    yes, quickEach() is essentially doing this externally, and passing that collection as the function’s `this` binding. But, again, that’s all masking what’s really going on to make it seem like it has anything to do with each() or quickEach().

    Consider this bare-bones test:
    http://jsperf.com/scriptjunkie-premature-3

    In that test, you’ll see I use .each() for both tests… the only difference is how I go about getting a $() wrapped `this`. And see the huge speed differences?

    Again, I’m glad you did this article. But I wish it wasn’t clouded in the discussion of quickEach… it should instead focus on (and complain about) why $(x) is so freakin slow. And that jQuery should get some sort of quick single-element wrapper $$(x) or something.

  2. Thanks, Kyle. Great feedback. I hadn’t dug in to why the $(x) was slow. What it sounds like is JQuery needs the equivalent of the Ext.fly() method. A high-performance, disposable version of the $(x) syntax.

    -Tim

Leave a Reply

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

*