Monday, January 24, 2011

"typeof " is fast ?

Recently, I read an article about the such called "micro optimizations". The article was about when to use typeof and when just to check against the undefined value, if you want to know whether a variable/property is undefined or not.

The conclusion was, to use typeof for any "global" variable and a direct conditional check against undefined within the scope of a function where you know the variables are at least declared. That makes sense, because if( some_undefined_variable !== undefined ) throws a reference error.

I played around with that in mind and I thought, this might be interesting for fellow Javascripters. My first testcase looked like this:

var loops = 1e6,
    foo;

console.time('typeof');
while(loops--) {
    if( typeof foo !== 'undefined' ) {}
}
console.timeEnd('typeof');

loops = 1e6;

console.time('direct');
while(loops--) {
    if( foo !== undefined) {}
}
console.timeEnd('direct');

with the result of:

typeof: 421ms
direct: 603ms

Pretty interesting. So here it looks like, typeof is faster than just comparing a variable against undefined. I couldn't really believe what I've seen here. I thought that is probably because I'm in the global context and my Javascript engine (SpiderMonkey) does some kind of wierd scope chain lookups for "undefined". Anyway, it should improve the overall performance to put that code into a function (-context), which I did:

(function() {
    var loops = 1e6,
        foo;

    console.time('typeof');
    while(loops--) {
        if( typeof foo !== 'undefined' ) {}
    }
    console.timeEnd('typeof');

    loops = 1e6;

    console.time('direct');
    while(loops--) {
        if( foo !== undefined) {}
    }
    console.timeEnd('direct');
}());

Results:

typeof: 54ms
direct: 185ms

As expected, this has a better overall performance, but still typeof is way faster than a plain conditional check vs. undefined. Well, the important thing I forgot here is to keep a local reference (some people call it: cache) for the undefined value:

(function(undef) {
    var loops = 1e6,
        foo;

    console.time('typeof');
    while(loops--) {
        if( typeof foo !== 'undefined' ) {}
    }
    console.timeEnd('typeof');

    loops = 1e6;

    console.time('direct');
    while(loops--) {
        if( foo !== undef) {}
    }
    console.timeEnd('direct');
}());

Final results:

typeof: 51ms
direct: 38ms

Yay! That's the way to go and that's why you always should try to cache objects / object propertys before accessing (if its not a problem to have a static reference).
My default Javascript opener when using jQuery looks like:

(function(win, doc, $, undef) {
    // lots of beautiful code that can access win(window), 
    // doc(document), $(jQuery) and undef(undefined)
}(this, this.document, jQuery));

However, to be honest I'm still not 100% sure why this affects the undefined value that much. Cached references are faster to access (fact), totally makes sense for objects and propertys, but the undefined value is not a property as far as I know.

update:

I was wrong. undefined really is a property in the global object (window).

'undefined' in window; // = true

This explains why the cached version of the undefined value is way faster. Thanks to Stackoverflow member CMS who pointed me on this fact here.

5 comments:

  1. sorry for the improper comment, but 1e6 is a pretty neat shortcut.

    ReplyDelete
  2. Script against right-click on a blog speaking on good programming... Defeat all author's credibility, don't you think ?

    ReplyDelete
  3. If I run the code outside the console, I get the following results:

    firefox (with 1e7):

    global context
    typeof: 228
    equals : 205

    function context
    typeof: 121
    equals : 94

    chrome (with 1e8):

    global context
    typeof: 235
    equals : 202

    function context
    typeof: 129
    equals : 102

    So equals seems actually faster.

    Example code:

    var benchmark = function() {

    var initloops = 1e7, foo, loops = initloops, t0, t1;
    t0 = Date.now();
    while(loops--) { if( typeof foo === 'undefined' ) {} }
    t1 = Date.now();
    console.log('typeof: ' + (t1-t0));

    loops = initloops;
    t0 = Date.now();
    while(loops--) { if( foo === undefined) {} }
    t1 = Date.now();
    console.log('equals : ' + (t1-t0));
    };

    var s = document.createElement('script');
    var code = benchmark.toString().split('\n');
    var glob = "console.log('global context');\n" + code.slice(1,code.length-1).join('\n');
    var clos = "console.log('function context');\n(" + code.join('\n') + '());';
    s.textContent = glob + '\n' + clos;
    document.head.appendChild(s);

    ReplyDelete
    Replies
    1. I haven't tried yet but I guess in 2011 browser implementation is not as fast as now (2015).

      Have you tried "1e6" and "1e5" ? If the result is different , it may have something to do with the "scale" then ;-)

      Delete