JS Parse and Execution Time
At Velocity NY, Daniel Espeset of Etsy gave a great talk about how Etsy profiles their JavaScript parse and execution time. Even better, after the talk, they released the tool on GitHub.
Daniel shared a few examples in his deck, but I couldn’t wait to take Daniel’s tool and fire it up on a bunch of random browsers and devices that I have sitting around.
For this test, I decided to profile just jQuery 2.1.1, which weighs in at 88kb when minimized. jQuery was selected for its popularity, not because it’s the worst offender. There are many libraries much worse (hey there Angular and your 120kb payload). The results above are based on the median times taken from 20 tests per browser/device combination.
The list of tested devices isn’t exhaustive by any means—I just took some of the ones I have sitting around to try and get a picture of how much parse and execution time would vary.
Device | Browser | Median Parse | Median Execution | Median Total |
---|---|---|---|---|
Blackberry 9650 | Default, BB6 | 171ms | 554ms | 725ms |
UMX U670C | Android 2.3.6 Browser | 168ms | 484ms | 652ms |
Galaxy S3 | Chrome 32 | 39ms | 297ms | 336ms |
Galaxy S3 | UC 8.6 | 45ms | 215ms | 260ms |
Galaxy S3 | Dolphin 10 | 2ms | 222ms | 224ms |
Kindle Touch | Kindle 3.0+ | 63ms | 132ms | 195ms |
Geeksphone Peak | Firefox 25 | 51ms | 109ms | 160ms |
Kindle Fire | Silk 3.17 | 16ms | 139ms | 155ms |
Lumia 520 | IE10 | 97ms | 56ms | 153ms |
Nexus 4 | Chrome 36 | 13ms | 122ms | 135ms |
Galaxy S3 | Android 4.1.1 Browser | 3ms | 125ms | 128ms |
Kindle Paperwhite | Kindle 3.0+ | 43ms | 71ms | 114ms |
Lumia 920 | IE10 | 70ms | 37ms | 107ms |
Droid X | Android 2.3.4 Browser | 6ms | 96ms | 102ms |
Nexus 5 | Chrome 37 | 11ms | 81ms | 92ms |
iPod Touch | iOS 6 | 26ms | 37ms | 63ms |
Nexus 5 | Firefox 32 | 20ms | 41ms | 61ms |
Asus X202E | IE10 | 31ms | 14ms | 45ms |
iPad Mini | iOS6 | 16ms | 30ms | 46ms |
Macbook Air (2014) | Chrome 37 | 5ms | 29ms | 34ms |
Macbook Air (2014) | Opera 9.8 | 14ms | 5ms | 19ms |
iPhone 5s | iOS 7 | 2ms | 16ms | 18ms |
Macbook Air (2014) | Firefox 31 | 4ms | 10ms | 14ms |
iPad (4th Gen) | iOS 7 | 1ms | 13ms | 14ms |
iPhone 5s | Chrome 37 | 2ms | 8ms | 10ms |
Macbook Air (2014) | Safari 7 | 1ms | 4ms | 5ms |
As you can see from the table above, even in this small sample size the parsing and execution times varied dramatically from device to device and browser to browser. On powerful devices, like my Macbook Air (2014), parse and execution time was negligible. Powerful mobile devices like the iPhone 5s also fared very well.
But as soon as you moved away from the latest and greatest top-end devices, the ugly truth of JS parse and execution time started to rear its head.
On a Blackberry 9650 (running BB6), the combined time to parse and execute jQuery was a whopping 725ms. My UMX running Android 2.3.6 took 652ms. Before you laugh off this little device running the 2.3.6 browser, it’s worth mentioning I bought this a month ago, brand new. It’s a device actively being sold by a few prepaid networks.
Another interesting note was how significant the impact of hardware has on the timing. The Lumia 520, despite running the same browser as the 920, had a median parse and execution time that was 46% slower than the 920. The Kindle Touch, despite running the same browser as the Paperwhite, was 71% slower than it’s more powerful replacement. How powerful the device was, not just the browser, had a large impact.
This is notable because we’re seeing companies such as Mozilla and Google targeting emerging markets with affordable, low-powered devices that otherwise run modern browsers. Those markets are going to dominate internet growth over the next few years, and affordability is a more necessary feature than a souped up device.
In addition, as the cost of technology cheapens, we’re going to continue seeing an incredibly diverse set of connected devices. With endless new form factors being released (even the Android Wear watches quickly got a Chromium based browser), the adage about not knowing where our sites will end up has never been more true.
The truly frightening thing about these parse and execution times is that this is for the latest version of jQuery, and only the latest version of jQuery. No older versions. No additional plugins or frameworks. According to the latest run of HTTP Archive, the median JS transfer size is 230kb and this test includes just a fraction of that size. I’m not even asking jQuery to actually do anything. Basically, I’m lobbing the browsers a softball here—these are best case results.
This re-affirms what many have been arguing for some time: reducing your dependency on JS is not healthy merely for the minor percentage of people who have JS disabled—it improves the experience for everyone. When anything over 100ms stops feeling instantaneous and anything over 1000ms breaks the users flow, taking 700ms to parse your JavaScript cripples the user experience before it really has a chance to get started.
So what’s a web developer to do?
Use less JavaScript. This is the simple one. Anything you can offload onto HTML or CSS, do it. JavaScript is fun and awesome but it’s also the most brittle layer of the web stack and, as we’ve seen, can seriously impact performance.
Render on the server If you’re using a client-side MVC framework, make sure you pre-render on the server. If you build a client-side MVC framework and you’re not ensuring those templates can easily be rendered on the server as well, you’re being irresponsible. That’s a bug. A bug that impacts performance, stability and reach.
Defer all the scripts. Defer every bit of JavaScript that you can. Get it out of the critical path. When it makes sense, take steps to defer the parsing as well. Google had a great post a few years back about how they reduced startup latency for Gmail. One of the things they did was initially comment out blocks of JavaScript so that it wouldn’t be parsed during page load. The result was a 10x reduction in startup latency. That number is probably different on today’s devices, but the approach still stands.
Cut the mustard. I’m a big fan of “cutting the mustard”, an approach made popular by the BBC. This doesn’t solve the problem of low-end devices with modern browsers, but it will make a better experience for people using less capable browsers. Better yet, by consciously deciding not to overwhelm less capable browsers with excess scripts you not only provide a better experience for those users, but you reduce the need for extra polyfills and frameworks for modern browsers as well. On one recent project where we did this, the entire JavaScript for the site was about 43% of the size of jQuery alone!
There are certainly cases to be made for JS libraries, client-side MVC frameworks, and the like, but providing a quality, performant experience across a variety of devices and browsers requires that we take special care to ensure that the initial rendering is not reliant on them. Frameworks and libraries should be carefully considered additions, not the default.
When you consider the combination of weight, parse time and execution time, it becomes pretty clear that optimizing your JS and reducing your site’s reliance on it is one of the most impactful optimizations you can make.