Archive for the ‘performance’ Category

Is a hash faster than a Switch in JavaScript?

Tuesday, August 17th, 2010

I stumbled across this concept recently and I thought I’d share it, because I don’t generally see this pattern being used. More importantly, I also share test results that show that maybe it’s not always a good idea to use this pattern…

The problem with Switch statements

The basic switch statement in JavaScript looks something like this:

var foo = 'c';

switch(foo) {
  case 'a':
  break;

  case 'b':
  break;

  case 'c':
  break;

  default:
}

So what’s wrong with this? The JS engine has to examine a bunch of unrelated cases until it finds the relevant one, executes the code, then breaks out of the switch because the job is done (this is why it’s important to break!). In the above example we had to go through case A and case B until finally reaching case C. What’s worse is that if it didn’t match any of these cases, the JS engine has to jump through ALL of the cases before it reaches Default, the fall-through case.

Actually it’s not so bad, as long as there are a limited number of cases. It’s probably no big deal if you only have a few cases to jump through. The problem gets bigger as your number of cases increases (some of you may know this as O(n)). What happens when there’s 10 cases? Then there’s potentially 10 checks on cases (assuming what ended up being executed was the default). 100 cases? Then potentially 100 checks.

What would be better is if there were a way to reduce the number of checks. One way would be to put the most frequently used cases at the top. This would alleviate some of the pain, but you still end up with extra processing while the JS checks each case. It would be ideal to avoid this extra processing altogether.

An alternative: The hash table

There is a way to avoid this extra processing! It’s by leading the code directly where it needs to go, without unnecessary checking of unrelated cases.

You can do this using a hash. In JavaScript we accomplish this with an object:

var foo = 'c';
var cases = {};
cases['a'] = function() {
  alert('I am A!');
};
cases['b'] = function() {
  alert('I am B!');
};
cases['c'] = function() {
  alert('I am C!');
}

if(typeof cases[foo] == 'function') {
  // only executes if we've defined it above
  cases[foo]();  // I am C!
} else {
  // default (the fallthrough)
}

There we go! No extra case checking here. We’ve led the JS straight to the code we want to execute!

Performance improvement…?

So.. this hash lookup seems faster in theory, but what about in practice? Unfortunately I ended up with some mixed results…

I created a simple performance test on jsperf.com and got these results:

Browser Switch ops/sec Hash table ops/sec % Difference
Chrome 6.0.490.1 dev 34,606,469 43,329,587 25% faster
Safari 5.0 16,777,216 10,854,824 35% slower
Opera 10.61 4,405,782 2,719,336 38% slower
Firefox 3.6.3 2,785,802 2,400,586 14% slower
IE6 147,870 206,869 40% faster
IE7 144,735 191,179 32% faster
IE8 350,085 472,417 35% faster
Mobile Safari (iOS4 on iPhone 3GS) 668,053 416,366 37% slower
Android (2.2 on Nexus One) 605,693 864,591 42% faster

* Ops/sec = Operations per second. Higher is better
** Chrome, Safari, Opera, and Firefox were tested on Mac OSX 10.6.4 2.53GHz Intel Core i5. IE tests were run on Windows 7 64bit 2.4GHz Quad Core

The Results

From the results, it looks like the hash optimization is only a benefit for Chrome, IE6-IE8, and Android. That’s quite a specific sampling. My guess is that the other browsers have implemented some sort of Switch statement optimizations that actually turn the hash optimization into an antipattern.

More info

Although I first read about this online, by no surprise this trick also appears in Nicholas Zakas’s High Performance JavaScript in a section on “Lookup Tables” (p. 72).

JavaScript SunSpider: HTC Evo versus HTC Incredible versus Nexus One

Thursday, June 10th, 2010

Result table

Test Evo (2.1) Incredible (2.1) Nexus One (2.2)
Total 16167ms 15237ms 5452ms
3D 2014ms 1835ms 946ms
Access 2126ms 1892ms 463ms
Bitops 1519ms 1591ms 360ms
Controlflow 243ms 206ms 20ms
Crypto 1033ms 1003ms 344ms
Date 1849ms 1896ms 639ms
Math 1684ms 1419ms 602ms
Regexp 1779ms 1673ms 155ms
String 3920ms 3722ms 1923ms

Thoughts

The Incredible is just slightly faster than the Evo, to the point where it’s probably negligible or within a margin of error. Both of these phones run on Android 2.1 with HTC’s Sense UI modifications, and represent the latest and greatest in Android phones available on the market today. Both run on the same 1GHz Snapdragon processor (QSD8650). The Nexus One is a bit older, and runs with an older version of the Snapdragon processor (QSD8250), however it still runs at 1GHz just like the other two phones.

As you can see the Nexus One blows away all the competition because it’s running Android 2.2 Froyo. These results were quite a shock to me and are quite impressive. These results even blow away Apple’s new iOS 4 running on my iPhone 3GS, which clocked in at a total time of 13787ms compared to the Nexus One’s startling 5452ms.

Testing methodology

Test: SunSpider 0.9.1

Devices: HTC Evo (Android 2.1), HTC Incredible (Android 2.1), HTC Nexus One (Android 2.2)

The SunSpider test was run five times on each phone. The phone was completely turned off and on before each test. The most extreme values of the five tests were thrown out, and the resulting four tests were averaged (sometimes from three tests when the values were very close together).

Raw results:

SunSpider HTC Evo results (5 tests)

SunSpider HTC Incredible results (5 tests)

SunSpider HTC Nexus One results (5 tests)

Related links

JavaScript SunSpider test: iOS 3.1.3 versus iOS 4 GM

JavaScript SunSpider test: iOS 3.1.3 versus iOS 4 GM

Thursday, June 10th, 2010

Result table

Test iOS 3.1.3 (3GS) iOS 4 GM (3GS) % change
Total 15396ms 13787ms -10.5%
3D 2411ms 1917ms -20.5%
Access 1884ms 1893ms +0.5%
Bitops 1044ms 1239ms +18.7%
Controlflow 143ms 221ms +54.5%
Crypto 982ms 850ms -13.4%
Date 1355ms 1065ms -21.4%
Math 2053ms 1511ms -26.4%
Regexp 1616ms 1916ms +18.6%
String 3908ms 3175ms -18.8%

Thoughts

After running these SunSpider tests, it looks like overall there’s significant speed gains between iOS 3.1.3 and iOS 4 GM. However, it’s concerning from these tests there were some things that actually ran slower on iOS 4. This either represents a real speed loss between the versions, a margin of error, or some flaw or inconsistency while testing. Or maybe I possibly have some wrong setting on my phone? Any input would be appreciated.

Testing methodology

Test: SunSpider 0.9.1

Device: iPhone 3GS

The test was run five separate times on the same phone for each version of the OS. The phone was completely turned off and on before each test.

The most extreme values of the five tests were thrown out, and the resulting four tests were averaged (sometimes from three tests when the values were very close together). I’m no statistics expert, so if you’d like to work it out for yourself, here are my raw test results:

SunSpider iOS 3.1.3 results (5 tests)

SunSpider iOS 4 results (5 tests)

Related links

Speed Test: iPhone 3GS Even Faster than Apple Claims

Video: John Resig – Testing, Performance Analysis, and jQuery 1.4

Monday, December 21st, 2009

In case you hadn’t seen it yet, John Resig was kind enough to stop by Yahoo! for the December Bayjax meetup. Here’s the video:

Usually developers are more interested in just getting the dang code to work, and as a result actual the testing and maintenance of JavaScript isn’t talked about too much, so I’m sure this will be new territory for many developers. And since it’s John Resig speaking, there was of course a bit about using TestSwarm, a testing distributed framework-agnostic automated testing tool (that’s a mouthful!).

Included in the talk are good things to note while testing, such as the fact unless you’re running Firefox or Chrome on Windows, all test times have a margin of error of up to 15ms (not to be confused with PPK’s observation of the delay between JavaScript computation and browser rendering).

(via YUIBlog)