Web Performance Calendar

The speed geek's favorite time of year
2011 Edition
Nicholas Zakas photo Nicholas C. Zakas (@slicknet) is a front-end consultant, author, and speaker. He worked at Yahoo! for almost five years, where he was front-end tech lead for the Yahoo! homepage and a contributor to the YUI library. He is the author of Professional JavaScript for Web Developers (Wrox, 2012), Professional Ajax (Wrox, 2007), and High Performance JavaScript (O'Reilly, 2010). Nicholas is a strong advocate for development best practices including progressive enhancement, accessibility, performance, scalability, and maintainability. He blogs regularly at http://www.nczonline.net/ and can be found on Twitter via @slicknet.

Web Storage has quickly become one of the most popular HTML5-related additions to the web developer toolkit. More specifically, localStorage has found a home in the hearts and minds of web developers everywhere, providing very quick and easy client-side data storage that persists across sessions. With a simple key-value interface, we’ve seen sites take advantage of localStorage in unique and interesting ways:

  • Disqus, the popular feedback management system, uses localStorage to save your comment as you type. So if something horrible happens, you can fire back up the browser and pick up where you left off.
  • Google and Bing store JavaScript and CSS in localStorage to improve their mobile site performance (more info).

Of the use cases I’ve seen, the Google/Bing approach is one that seems to be gaining in popularity. This is partly due to the difficulties of working with the HTML5 application cache and partly due to the publicity that this technique has gained from the work of Steve Souders and others. Indeed, the more I talk to people about localStorage and how useful it can be for storing UI-related information, the more people I find who have already started to experiment with this technique.

What I find intriguing about this use of localStorage is that there’s a built-in, and yet unstated, assumption: that reading from localStorage is an inexpensive operation. I had heard anecdotally from other developers about strange performance issues, and so I set out to quantify the performance characteristics of localStorage, to determine the actual cost of reading data.

The Benchmark

Not too long ago, I created and shared a simple benchmark that measured reading a value from localStorage against reading a value from an object property. Several others tweaked the benchmark to arrive at a more reliable version. The end result: reading from localStorage is orders of magnitude slower in every browser than reading the same data from an object property. Exactly how much slower? Take a look at the following chart (higher numbers are better).

Chart comparing reading from an object to reading from localStorage

You may be confused after looking at this chart because it appears that reading from localStorage isn’t represented. In fact, it is represented, you just can’t see it because the numbers are so low as to not even be visible with this scale. With the exception of Safari 5, whose localStorage readings actually show up, every other browser has such a large difference that there’s no way to see it on this chart. When I adjust the Y-axis values, you can now see how the measurements stack up across browsers:

Chart comparing reading from an object to reading from localStorage

By changing the scale of the Y-axis, you’re now able to see a true comparison of localStorage< versus object property reads. But still, the difference between the two is so vast that it's almost comical. Why?

What's Going On?

In order to persist across browser sessions, values in localStorage are written to disk. That means when you're reading a value from localStorage, you're actually reading some bytes from the hard drive. Reading from and writing to a hard drive are expensive operations, especially as compared reading from and writing to memory. In essence, that's exactly what my benchmark was testing: the speed of reading a value from memory (object property) compared to reading a value from disk (localStorage).

Making matters more interesting is the fact that localStorage data is stored per-origin, which means that it's possible for two or more tabs in a browser to be accessing the same localStorage data at the same time. This is a big pain for browser implementors who need to figure out how to synchronize access across tabs. When you attempt to read from localStorage, the browser needs to stop and see if any other tab is accessing the same area first. If so, it must wait until the access is finished before the value can be read.

So the delay associated with reading from localStorage is variable - it depends a lot on what else is going on with the browser at that point in time.

Optimization Strategy

Given that there is a cost to reading from localStorage, how does that affect how you would use it? Before coming to a conclusion, I ran another benchmark to determine the effect of reading different size pieces of data from localStorage. The benchmarks saves four different size strings, 100 characters, 500 characters, 1000 characters, and 2000 characters, into localStorage and then reads them out. The results were a little surprising: across all browsers, the amount of data being read did not affect how quickly the read happened.

I ran the test multiple times and implored my Twitter followers to get more information. To be certain, there were definitely a few variances across browsers, but none that were large enough that it really makes a difference. My conclusion: it doesn't matter how much data you read from a single localStorage key.

I followed up with another benchmark to test my new conclusion that it's better to do as few reads as possible. The results correlated with the earlier benchmark in that reading 100 characters 10 times was around 90% slower across most browsers than reading 10000 characters one time.

Given that, the best strategy for reading data from localStorage is to use as few keys as possible to store as much data as possible. Since it takes the roughly same amount of time to read 10 characters as it does to read 2000 characters, try to put as much data as possible into a single value. You're getting hit each time you call getItem() (or read from a localStorage property), so make sure that you're getting the most out of the expense. The faster you get data into memory, either a variable or an object property, the faster all subsequent actions.