As a web developer you know that creating engaging web UX means quickly delivering content that captures your visitor's attention.
But it turns out you can also use localStorage to make your mobile website faster!
What does this mean? It means you should absolutely be using localStorage for essential resources rather than depending on the browser's cache. This is a bold claim I'm happy to back with data, and I'm also very curious to hear from the community on your own test results. For more details on how I collected and analyzed my results, read on.
Background: the web community weighs in on localStorage
- localStorage is synchronous (can block rendering)
- localStorage performs file I/O (unbounded worst case performance)
- Browsers load the data into memory on the first request (wastes user-agent RAM)
- localStorage is persistent (if you never visit a site again, disk space is wasted)
These criticisms notwithstanding, localStorage remains attractive for mobile web developers for several reasons:
- Browser native caches are shared between websites and are often small on mobile devices - as Guy Podjarny has determined, sometimes smaller than 5MB
- Some browser caches are not persistent (contents lost when the app exits)
- Native browser cache performance & behavior of mobile user-agents is highly variable
- Latency on mobile is high - good caching is required for good performance
- localStorage content eviction policy is directly under developer control, browser native cache is not
That localStorage is synchronous, performs file I/O, uses RAM & storage on user-agents is not in dispute. However, what is important is to find out what the performance is like in actual smartphone browsers executing scripts on real websites in the wild.
Condition 1: browser native cache performance
The objective of condition 1 was to benchmark performance of the browser native cache on smartphones. In this condition, the JS payload is loaded twice, the first time to populate the browser cache and the second to measure timing for instantiating a script from browser cache. The payload is delivered with both a one month future
CACHE-CONTROL: max-age=2592000 headers set (if you're not familiar with cache control headers, or want a refresh, this is a great introduction). In this condition the content of the payload is set using the
src= attribute of a
Condition 2: localStorage performance
script tag as a textnode.
Experiment validity threats
The most significant threat to the validity of this experiment is that in both conditions I am retrieving the data immediately after writing it. A subsequent test might involve reading data several days later when other browsing activity has taken place. However -- this modified experiment is unlikely to favor the native cache as it is much more likely that content will be evicted from the native cache after several days of user browsing activity. localStorage does not suffer from this eviction problem. In this respect, I believe these results understate the performance improvement available from using localStorage.
The second validity threat is that performance may vary if localStorage is fully utilized. Will Chan measured desktop localStorage performance for relatively small storage sizes - and saw very good results on desktop as well. What's interesting is that he found that performance degrades considerably as the amount of data stored in localStorage in desktop Chrome increases. More testing is required to determine the impact of a heavily utilized localStorage cache on mobile.
For data analysis, I applied a 20% trimmed mean to both native browser cache and localStorage conditions discarding 10% of the lowest and highest measured results. This was done to reduce the sensitivity of the results to outliers and is considered a robust estimator.
The figure above summarizes my results. As you can see the mean performance of localStorage is better than browser native cache behaviour in all cases. In many notable cases, such as iOS 5 and iOS 6, localStorage outperforms mean native cache performance by a factor of 5. Summary values are in the table below.
Variance in localStorage performance is much smaller than browser native cache variance. This can be seen by the much smaller confidence interval present in the localStorage condition. It's also readily apparent in the standard deviation column of the table above. This is especially true for Android 2.2 devices where native cache behaviour varies wildly, but the effect is present in all devices, including newer smartphones running both the iOS and Android operating systems.
On Android the minimum time (bottom whisker on the candlestick plot) measured for native cache performance beats the minimum time from localStorage. This suggests that under ideal circumstances the native cache is faster, but my real world tests suggest these ideal circumstances are the exception rather than the rule and mediocre or worst-case behavior from the native cache is common. On iOS 5 and iOS 6 the minimum load time for localStorage and native browser behaviour are nearly identical, but mean time for localStorage is significantly faster.
For completeness, I've included in the appendix the histogram data from each of the OS versions measured for both browser native cache and localStorage conditions. Features of note are described in the captions. These graphs provide more insight into the mediocre & worst-case behavior of native caches and localStorage.
localStorage is an attractive alternative to the mobile browser's native cache for critical page resources with much better average-case performance (2-5x better for recent OS releases) than the native cache. Because web developers have explicit control over the eviction strategy from localStorage, key resources can be preserved or updated in a more optimal way than is possible with generic native browser caching strategies.
There are very significant performance gains to be realized by using localStorage. Keep in mind, however, that there are also tradeoffs when using localStorage.
- localStorage space is relatively small, 5MB on most browsers, and it can only store string data. This is effectively halved because localStorage stores strings as double-byte characters (UTF-16).
- localStorage reads and writes do block the rendering thread. Where possible reads/writes should be done after initial page rendering is complete. You may want to avoid operations that are likely to cause performance problems such as a very large numbers of operations (thousands or tens of thousands of reads or writes), as well as read/write operations larger than 1MB.
- Browsers should do a better job of exposing storage space used by websites taking advantage of the web storage APIs to smartphone users. Be a good neighbour and don't use localStorage recklessly, use it for key resources that are in your critical path.
- You'll need to manage your website's cache eviction & resource updates to localStorage manually.
Do you use localStorage API in your work? What do you use it for and how does it perform?
Let me know by leaving a comment!
Additional data plots are available in the appendix.