Friday, October 23, 2015

Performance of the NOT operator in Javascript when used to verify existence or if a value is set

I had this Javascript code that I was trying to write as tight as possible and I realized that I don't know if using "!" on an object to check if it is set to a value is slow or fast. I mean, in a strong typed language, I would compare the object with null, not use the NOT operator. So I randomly filled an array with one of five items: null, undefined, empty string, 0 and new Date(), then compared the performance of a loop checking the array items for having a value with the NOT operator versus other methods. I used Chrome 48.0.2564.109 m, Internet Explorer 11.0.9600.18163 and Firefox 44.0.2 for the tests.

Fast tally:
  • NOT operator: 1600/51480/1200ms
  • === 0 (strong type equality): 1360/47510/2180ms
  • === null : 550/45590/510ms
  • == with 0: 38700/63030/131940ms
  • == with null: 1100/48230/900ms
  • === used twice(with 0 and null): 1760/69460/3500ms
  • typeof == 'object' (which besides the Dates also catches null): 1360/382980/1380ms
  • typeof === 'object' (which besides the Dates also catches null): 1370/407000/1400ms
  • instanceof Date: 1060/69200/600ms

Thoughts: the !/NOT operator is reasonably fast. Using normal equality can really mess up your day when it tries to transform 0 into a Date or viceversa (no, using 0 == arr[i] instead of arr[i] == 0 wasn't faster). Fastest, as expected, was the strong type equality to null. Surprising was the null equality, which catches both null and undefined and is second place. typeof was also surprising, since it not only gets the type of the object, but also compares the result with a string. Funny thing: the === comparison in the case of typeof was slower than the normal == comparison for all browsers, so probably it gets treated as a special construct.

It is obvious that both Chrome and Firefox have really optimized the Javascript engine. Internet explorer has a 18 second overhead for the loops alone (so no comparison of any kind), while the other browsers optimize it to 300ms. Sure, behind the scenes they realize that nothing happens in those loops and drop them, but still, it was a drag to wait for the result from Internet Explorer. Compared with the other huge values, the ===null comparison is insignificantly smaller than all the others on Internet Explorer, but still takes first place, while typeof took forever! Take these results with a grain of salt, though. When I was at FOSDEM I watched this presentation from Firefox when they were actually advising against this type of profiling, instead recommending special browser tools that would do that. You can watch it yourselves here.

Final conclusion: if you are checking if an object exists or not, especially if you can insure that the value of a non existent object is the same (like null), === kicks ass. The NOT operator can be used to check a user provided value, since it catches all of null, undefined, empty space or 0 and it's reasonably fast.

Here is the code:
var arr=[];
for (var i=0; i<100000; i++) {
    var r=parseInt(Math.random()*5);
    switch(r) {
        case 0: arr.push(null); break;
        case 1: arr.push(undefined); break;
        case 2: arr.push(''); break;
        case 3: arr.push(0); break;
        case 4: arr.push(new Date()); break;
    }
}

var n=0;
var start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (!arr[i]) n++;
    }
}
var end=performance.now();
console.log('!value                    '+n+': '+(end-start));

n=0;
start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (arr[i] === 0) n++;
    }
}
end=performance.now();
console.log('value===0                 '+n+': '+(end-start));

n=0;
start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (arr[i] === null) n++;
    }
}
end=performance.now();
console.log('value===null              '+n+': '+(end-start));

n=0;
start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (arr[i] == 0) n++;
    }
}
end=performance.now();
console.log('value==0                  '+n+': '+(end-start));

n=0;
start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (arr[i] == null) n++;
    }
}
end=performance.now();
console.log('value==null               '+n+': '+(end-start));

n=0;
start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (arr[i] === 0 || arr[i] === null) n++;
    }
}
end=performance.now();
console.log('value===0 || value===null '+n+': '+(end-start));

n=0;
start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (typeof(arr[i])=='object') n++;
    }
}
end=performance.now();
console.log('typeof(value)==\'object\'   '+n+': '+(end-start));

n=0;
start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (typeof(arr[i])==='object') n++;
    }
}
end=performance.now();
console.log('typeof(value)===\'object\'  '+n+': '+(end-start));

n=0;
start=performance.now();
for (var j=0; j<1000; j++) {
    for (var i=0; i<100000; i++) {
        if (arr[i] instanceof Date) n++;
    }
}
end=performance.now();
console.log('value instanceof Date     '+n+': '+(end-start));

0 comments: