Web Performance Calendar

The speed geek's favorite time of year
2012 Edition
ABOUT THE AUTHOR
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.

If 2012 will be remembered for anything specific in the web development world, the debate over performance issues with localStorage will surely be high on the list. The debate began with a post by Christian Heilmann entitled, There is no simple solution for localStorage, in which he made several claims about poor localStorage performance and called for changes to the existing API or the development of an alternative API.

The problem with the article was that it didn’t have any numbers, so that set off a large number of follow-up articles taking up the task of providing numbers. John Allsopp wrote localStorage perhaps not so harmful, in which he measured the amount of time it took to read and write 10KB using localStorage. I also entered the mix with an my article, In defense of localStorage, in which I compared the read and write performance of localStorage to that of cookies (which have similar performance characteristics due to disk access). We both determined that localStorage didn’t seem slow enough to warrant throwing it away.

After catching up with Taras Glek from Mozilla (who wrote his own post on the subject, PSA: DOM localStorage considered harmful), I realized that both John’s benchmark and my own were flawed in their approach due to the way that localStorage actually works.

The key issue with localStorage is that it’s a synchronous operation that does file I/O. All of the data stored in localStorage is written to disk in order to persist across sessions. As anyone who has worked with Node.js realizes, file I/O is expensive and inconsistently so. There are any number of processes that could be accessing files at any point in time. For example, have you ever noticed how your computer slows down while an antivirus program is run? In a perfect world, there is no other process trying to access the file at the same time that you are in the read happens quickly. In the worst case, you have to wait for a lock to be released on the file before it can be read.

This introduces a problem for browsers: when should the data be read from disk? There are really only two options. First, the data could be read during page load to make sure that all subsequent read operations are fast. Of course, that would mean that localStorage would affect page load time even if it’s never used during the page lifecycle. In the perfect case, you wouldn’t notice much of a difference, but in the worst case, you could be adding milliseconds (or seconds) to the page load time.

The second approach is to wait until localStorage is accessed for the first time and then read the data from disk. This prevents any interruption of page load time but does mean that the browser will freeze when the first read happens. All of the data in the file is read into memory so each subsequent read from localStorage is fast. Likewise, writes our first stored into memory and then written to disk later so that is fast. Both Firefox and Chrome take this approach (Opera appears to as well, but I haven’t confirmed), which makes it hard to measure localStorage performance using a tool like jsPerf that never loads the page.

So the issue is clear: the time it takes for the first called to localStorage.getItem() is unpredictable. Additionally, it is a blocking call, so the browser freezes while waiting for data to be read from disk. The benchmarks that John and I used grouped together the first, slow read with all of the subsequent fast reads, so our numbers don’t really mean much. Recently, Chromium engineer William Chan did some analysis on localStorage performance measuring the first read.

Williams findings showed that reads were very fast through the 75th percentile on Windows, Mac, and Linux. In fact, it was only in the 99th percentile that the initial read took longer than a second on Windows and Linux (still less than a second on Mac). See the figure below.

William Chan's localStorage Read Performance Numbers
William Chan’s localStorage Read Performance Numbers

Taras noted in a Google+ comment that he saw a similar performance characteristics and Firefox. So it seems like Taras and William have proven that the performance issues around localStorage aren’t as dire as first thought.

Still, I decided to do some testing on my own. I measured the first read from localStorage and then subsequent reads using performance.now() if available and Date objects otherwise. Here’s what I found (on Windows 7 unless otherwise noted):

  • On Chrome, the first read takes ~1ms and subsequent reads take 0ms.
  • On Firefox, the first read takes ~0.5ms and subsequent reads take ~0.1ms.
  • On Internet Explorer 9, all reads take 0ms. I’m not sure how Internet Explorer is loading this data, but it doesn’t appear to be the same as with other browsers.
  • On Opera, first read takes ~1ms and subsequent reads take 0ms.
  • For Safari on iOS 6, the very first read takes up to 24ms and subsequent reads take 0ms. The interesting thing here is that Safari appears to keep everything in memory and never reads from disk again until the application is shut down. As long as Safari remains running, reads continue to take 0ms.

At this point, I don’t see any reason not to use localStorage on the desktop. Yes, there are some poor numbers in the 99th percentile, but there will always be outliers to the norm. On average, performance seems pretty good across desktop browsers even on that initial read.

For mobile, there’s more research needed. Clearly disk operations on a mobile device are going to be more expensive than on the desktop and you should try to limit those as much as possible. However, at least iOS is somewhat mitigating the cost by only doing it once per application session. Since most users won’t be shutting down Safari on a regular basis, you need to decide if it’s worth taking the hit for that initial read or not. If 24 ms is faster than what it would take to get the same resource from the server, then it may well be worth the hit to store that data in localStorage.

Overall, I still feel that the cries of bad localStorage performance were premature. The data that is now available indicates that performance isn’t as horrible as some of the early blog posts posited. Yes, disk I/O always has the potential to be slow, but I think this was a good example of why it’s important to get actual numbers before warning people away from what has arguably become one of the most popular HTML5 APIs. There are still a lot of sites relying on this functionality and none that have come out publicly to say that localStorage performance was a problem. I know I will still use it without worrying about performance issues, although I will definitely think twice before using it during page load.