Each Second Counts, or Not

by Shea Frederick on July 29th, 2010

Every once and a while I glance upon a chunk of code that just screams at me to be optimized. From that point on, all I can do is think about how much faster it would be if I just did this or that. Today I had one of those moments when looking at a co-workers code - I noticed that they had scoped an each loop that did not use scope (context). This got me wondering if there were any optimizations within the each loop that made it run faster without scope, so I took a look.

Call & Scope

So as it turns out, the simple answer is no. The each method uses the call method of the Function constructor to apply scope to the function executed on each pass of the loop. When no scope is provided to the each method, it uses the current item as the scope - which is kinda strange, but whatever.

No Scope

Here is my smart idea - don't use the call method if there is no scope provided. Simple, and it seems like it would help speed things up, so let's give it a shot.

The basic change here is on line 9 where I check to see if scope was passed in, and decide to either call the function to apply scope or just execute it directly.

Using my current web app as the guinea pig, this version ran between 5-18% faster in Firefox than it's non scope checking predecessor. Internet Explorer's results were so far scattered across the board that I could not draw a conclusion - in some tests 50% faster, in others 350% slower. As usual, IE left me confused, so im just going to ignore that for now.

Can We Do Better?

The next thing that caught my eye was the fact that when a single item is passed into the each method, it is shoved into an array and sent through the for loop - this seemed like a place for improvement.

In this case I have simply taken any single non array item that is passed in and executed the function immediately, bypassing the for loop. Results of this change were not noticeable, and matched almost identically with my previous version. Upon closer inspection, it turns out that this case is never reached, in other words, there is always an array passed in. Go figure.

Summary

The moral of the story here is to think simple when trying to optimize code, otherwise we end up going to far into optimizing and actually make the code more complex and likely slower, but this does not mean that the code with the fewest characters or lines will be the quickest. Experimenting is fun, so I would encourage everyone to try things like this just to see how they work and in turn gain a better understanding of how the methods you use every day actually work. Priceless.

Code for these tests can be downloaded from my Ext.ux.Misc Git repo on Github.

My next step is to test each loop "unrolling" to see what the effect is...let me know what you have tried on your own, and how it has worked out for you.

4 Comments
  1. James Dempster permalink

    I wonder how much faster it would be if we moved the if statement outside of the loop.


    Ext.each = function(array, fn, scope){
    if(Ext.isEmpty(array, true)){
    return;
    }
    if(!Ext.isIterable(array) || Ext.isPrimitive(array)){
    array = [array];
    }
    if (scope) {
    for(var i = 0, len = array.length; i < len; i++){
    if(fn.call(scope, array[i], i, array) === false){
    return i;
    }
    }
    } else {
    for(var i = 0, len = array.length; i < len; i++){
    if(fn(array[i], i, array) === false){
    return i;
    }
    }
    }
    };

    • Thanks James. There is just a part of me that hates duplicating common code for the sake of a var existing or not. Ive done it before and it always came back to bite me in the ass.

Trackbacks & Pingbacks

  1. Tweets that mention Each Second Counts, or Not | VinylFox -- Topsy.com
  2. Each Second Counts, or Not at found_drama

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS