JavaScript performance
Sunday, May 1, 2011 2:19:55 PM
I work in the core-gfx team here at Opera and are currently working on our WebGL implementation.
I've only been with Opera for a little less than two years following a far too long time in the
games industry. So far I'm loving every minute in the browser business.
I've been working on a WebGL demo on my spare time for a while and just like I've always been doing when writing games in C++ I wanted to have my assets all neatly wrapped up in a zip archive.
It's very handy, reduces disk footprint, loading time, organizes your files and above all with shell/explorer-integration it's so easy to work with even your most technology ignorant artist, designers and producers can handle it.
Since I couldn't find any javascript library that did that for me I decided to write one myself. I took the excellent C library tinf (tiny inflate) by Jørgen Ibsen, spent a few hours porting it to javascript, a few more to work out why my ported pointer arithmetic didn't work, added a bit of PKZIP archive management source and voila.
It worked like a charm... in Opera. Now, this is where the story takes a turn. This post isn't about my seriously cool WebGL demo, or about how great my little JSUnzip library is, it's about javascript performance and whether Crockford is right in that we're all so busy tuning our browsers to SunSpider and the likes that real-world implementations suffer.
JSUnzip is a straight port of one of the most widely used applications in the world. So if SunSpider, Kraken and V8 are truly indicative of real world performance I'd see the browsers all perform within a reasonable range of each other when unpacking the Canterbury Corpus zip. They didn't. In fact the numbers were so off that I instantly assumed something was wrong with the test. Firefox clocked in at 10 times slower than Opera, Chrome at 60 times slower and Safari at a whopping 100 times slower. (I really tried my best at including IE but it just won't work with XHR and binary data. If you know how to make it, let me know!) When you see results like these you immediately expect them to be wrong, but I both double and triple checked them, verified output, ran it over and over again and asked colleagues to check it out. Intrigued by the results I decided to see if I could zone in on the performance hotspot. The easiest would be the two webkit browsers since they were by far the ones suffering the most. A few hours later I had distilled the inner loop and came up with this test case.
You can try the live test-case in your favourite browser here: webkit hotspot test-case.
When I ran it on my box I got these results:
WebKitters, please optimize that hotspot so I can use JSUnzip in my demo
and while I'm on the air, kudos to the Opera ES-team for Carakan, Opera's awesome JS engine. As always it's taking names and kicking ass.
I've been working on a WebGL demo on my spare time for a while and just like I've always been doing when writing games in C++ I wanted to have my assets all neatly wrapped up in a zip archive.
It's very handy, reduces disk footprint, loading time, organizes your files and above all with shell/explorer-integration it's so easy to work with even your most technology ignorant artist, designers and producers can handle it.
Since I couldn't find any javascript library that did that for me I decided to write one myself. I took the excellent C library tinf (tiny inflate) by Jørgen Ibsen, spent a few hours porting it to javascript, a few more to work out why my ported pointer arithmetic didn't work, added a bit of PKZIP archive management source and voila.
It worked like a charm... in Opera. Now, this is where the story takes a turn. This post isn't about my seriously cool WebGL demo, or about how great my little JSUnzip library is, it's about javascript performance and whether Crockford is right in that we're all so busy tuning our browsers to SunSpider and the likes that real-world implementations suffer.
JSUnzip is a straight port of one of the most widely used applications in the world. So if SunSpider, Kraken and V8 are truly indicative of real world performance I'd see the browsers all perform within a reasonable range of each other when unpacking the Canterbury Corpus zip. They didn't. In fact the numbers were so off that I instantly assumed something was wrong with the test. Firefox clocked in at 10 times slower than Opera, Chrome at 60 times slower and Safari at a whopping 100 times slower. (I really tried my best at including IE but it just won't work with XHR and binary data. If you know how to make it, let me know!) When you see results like these you immediately expect them to be wrong, but I both double and triple checked them, verified output, ran it over and over again and asked colleagues to check it out. Intrigued by the results I decided to see if I could zone in on the performance hotspot. The easiest would be the two webkit browsers since they were by far the ones suffering the most. A few hours later I had distilled the inner loop and came up with this test case.
// Hotspot testing. Backreference copy.
var dest = 'apple';
while (dest.length < 100000) {
var offs = dest.length - 5;
for (var i = offs; i < offs + 10; ++i)
dest += dest[i];
}
It's a dead simple inner loop which is something you see in the deflate algorithm. It
copies an earlier occurrence of characters from a string and adds it to the end.
You can try the live test-case in your favourite browser here: webkit hotspot test-case.
When I ran it on my box I got these results:
Firefox 4.01 - 8ms Opera 11.10 - 13ms Internet Explorer 9 - 26ms Chrome 11 - 2230ms Safari 5.05 - 4685msApparently this was not the same hotspot that Firefox hit in the full unzip test as it's soaring through this, but it's spot on for Chrome and Safari. For this dead simple little test we're seeing the fastest browser being more than a factor 500 faster than the slowest one. That's pretty significant. So what does this mean? Not a whole lot in the second browser war at least. I'm sure you could construct more of these where the results would single out another browser. The notable thing though is that my unsuspecting real-world usage hit such a significant difference between browsers when we're currently battling it out in media over milliseconds in SunSpider. Is Crockford right? Maybe. In any instance I don't think it would hurt to revisit the javascript performance test suites.
WebKitters, please optimize that hotspot so I can use JSUnzip in my demo
and while I'm on the air, kudos to the Opera ES-team for Carakan, Opera's awesome JS engine. As always it's taking names and kicking ass.


Martin KadlecBS-Harou # Sunday, May 1, 2011 3:46:43 PM
António Afonsoantonioafonso # Sunday, May 1, 2011 8:19:41 PM
Andreas FarreAndreasF # Sunday, May 1, 2011 8:40:35 PM
Joejoelangeway # Monday, May 2, 2011 4:31:56 PM
Matt Pennigpennig # Monday, May 2, 2011 5:09:12 PM
var dest = 'apple';
while (dest.length < 100000) {
var out = '';
for (var i = dest.length - 5, end = i + 10; i < end; ++i)
out += dest\[i\];
dest += out;
}
Forgive the escaped braces. It tried to turn it into italics.
ithinkihaveacat # Monday, May 2, 2011 5:57:26 PM
http://jsperf.com/backreference-copy
The variation between engines is quite surprising.
Ilidioilidiomartins # Monday, May 2, 2011 6:00:48 PM
Firefox 4: 12 ms
Opera 11.10: 28 ms
Chrome 11: 2579 ms ~ 2.579 s
Safari 5: 233226 ms ~ 233.226 s
azakai # Monday, May 2, 2011 6:31:24 PM
A comment about compressing in JavaScript - you can compile zlib (or any other compression library) from C to JavaScript using Emscripten (in fact zlib is in Emscripten's automatic tests). That might be easier than manually porting a library.
Eike eikes # Monday, May 2, 2011 6:49:23 PM
http://blog.nihilogic.dk/2008/05/compression-using-canvas-and-png.html
Tom Robinsontlrobinson # Monday, May 2, 2011 10:04:52 PM
Erik Mölleremoller # Tuesday, May 3, 2011 6:57:11 AM
In any instance, the fact that there was such a huge difference in performance on something as elementary as string concatenation, something seen in just about every javascript out there today, was intriguing and I think the post served its purpose well and sparked a lot of interesting discussions... hopefully making the interweb a better place.
LennieSilentLennie # Sunday, May 8, 2011 8:07:54 PM
http://twitter.com/#!/paulrouget/status/26961510848790528
He found these:
http://blog.renevier.net/index.php?post/2011/01/07/js-library-to-read-zip-file
http://fhtr.blogspot.com/2010/05/loading-targz-with-javascript.html
http://fhtr.blogspot.com/2010/05/parsing-tarballs-with-javascript.html
Also in Firefox you can also try their JavaScript extension for handling binary data to see how much extra performance that will get you:
https://developer.mozilla.org/en/JavaScript_typed_arrays
Have a nice day.
ms7821 # Sunday, May 8, 2011 8:40:02 PM
I'm also not sure arrays help that much. By far the fastest method is substring, which presumably translates directly into the OS string/character copy function.
Tests here:
http://jsperf.com/emollerweird
Remy Sharpremysharp # Sunday, May 8, 2011 9:55:26 PM
From what I can see, it's just the speed increase that Webkit has had over the years that's simply masking, what appears to be the same problem.
Basically, I changed your hotspot testing to use an array instead of concatenation and performance gets lots better - i.e. Safari down from 4seconds to 7ms, and Chrome down from 2.5seconds to 10ms - really the way it should be.
Working example: http://jsbin.com/aruze3 (source: http://jsbin.com/aruze3/edit )
Basically, instead of the var dest = 'apple', I use var dest = 'apple'.split('') and instead of dest += dest, I use dest.push(dest);
Then once you're done, a one hit puts the string back together via dest = dest.join('');
This simply proves that string concatenation is just as crap as it's always been, only to, browsers got faster, so the problem appears to go away. Under the hood it may be something entirely different - but this sure smells like the old school concat issues.
Charles SchlossChas4 # Sunday, May 8, 2011 11:04:01 PM
Remy Sharpremysharp # Sunday, May 8, 2011 11:49:08 PM
Rod Knowltoncodelahoma # Monday, May 9, 2011 12:06:53 AM
Seems odd that an indexed reference into a string isn't just aliased to substr(i,1), though.
Am I missing a reason why it wouldn't be implemented as such?
Sam Lalanisamlalani # Thursday, May 12, 2011 3:35:49 AM
function getRect (object)
{
var left = object.offsetLeft;
var top = object.offsetTop;
var width = object.offsetWidth;
var height = object.offsetHeight;
return ({x:left, y:top, w:width, h:height});
}
Erik Mölleremoller # Friday, May 13, 2011 1:13:36 PM