As a web developer, you’re likely well aware that a key rule to high performance web design is to make fewer HTTP requests — especially on smartphones, where high latencies are the norm.
You may also know that data URIs have long been considered a web performance best practice for reducing these requests.
So you can imagine my surprise to discover, when measuring the performance of hundreds of thousands of mobile page views, that loading images using a data URI is on average 6x slower than using a binary source link such as an img tag with an src attribute!
In this article, I’ll discuss the different use cases of both data URIs and binary images, review my experiment results, and suggest future best practices for both using data URIs and building high performance mobile websites.
A quick refresher on Data URIs
The data URI scheme, formalized in rfc 2397, allows developers to include image data inline in HTML or in CSS. Because the image data is included in the HTML or CSS directly, no HTTP request is needed to materialize the image when you use the data URI scheme.
Data URI vs. Binary Image Materialization
Originally developed for email, data URIs have been used in the web world for several years, and have widespread support in popular browsers. For an introduction to data URIs, check out Nicholas Zakas’ great post on the subject.
Data URIs use an encoding scheme called base64 originally designed for including binary data in email. The base64 encoding scheme translates binary data into a 6-bit format that is compatible with 7-bit ASCII terminals.
This translation does not come free of charge; data URIs suffer a number of seldom discussed penalties:
- Base64 encoding incurs transfer size overhead of 1.37 times the original data, with another 814 bytes of header data. Server gzip compression reduces this overhead to around 3% so the penalty is relatively small.
- The content must be decoded back into it’s original form by the browser. This operation consumes CPU & battery on mobile devices.
- If data URIs are included in HTML or uncached CSS, the content of the data URI will be sent to the browser from the HTTP server with each request.
- Regardless of whether the data URI content exists in a cached CSS or HTML file, the browser must decode the image each time a page renders: the decoding cost is paid repeatedly every time a page is viewed.
Binary images, on the other hand, require no extra decoding step and are fast for browsers to materialize. The downside of binary images is the connection overhead required to download them. This connection overhead can be very expensive — a recent blog post from Tammy Everts showed that time to first byte (TTFB) on mobile can be up to 400ms for smartphones even when you use a CDN.
But looking at the worst case scenario for binary images doesn’t reveal that the typical case can be much better:
- If you use good caching rules, you’ll only ever pay the connection overhead once, on first page load.
- Recent browsers use optimizations to improve networking performance, in particular connection keep-alive and pipelining, as well as establishing multiple server connections. If you’ve done a good job optimizing your pages, chances are the image is being downloaded from a warm, established connection, reducing TTFB and increasing throughput.
- You can bundle all of your small, persistent images together into a single cacheable request using a CSS sprite.
For this experiment I performed a Real User Monitoring (RUM) test using 436 different active mobile websites and made roughly 230,000 trials. There were two experimental conditions; in both conditions an image element was created and the src attribute specified.
In the first condition, the src image attribute was specified to an image location known to be in the browser cache. This is called the binary condition.
In the second condition, the src image attribute was specified to a pre- fetched data URI of the same image as in the first condition. This is called the data URI condition.
Both conditions used the same image, a 17.8kB PNG. In both conditions, the same image was materialized 5 times. Materialization completion was measured by using the “load” event of the image object.
When the load event for all 5 images fired, the test was marked as “complete” and results were recorded. Time measured was the aggregate total time for 5 materializations. Each trial was executed on a different browser. The experiment executed after all other scripts in the page, but before the domready event. This simulates loaded conditions for the phone browser, as rendering would be nearing completion but not yet finished.
Data Analysis & Results
The candlestick plot you see below shows mean (green mark), standard deviation (blue bar) as well as the high / low values seen (light blue whisker).
A 20% trimmed mean was applied to clean up the data. As you can see from the candlestick plot above, the mean time for binary image materialization is substantially smaller.
For recent, high-end smartphones such as those running iOS 6 or Android 4.2, binary image materialization is about 6x faster. For older, slower hardware, binary image materialization is 10x faster. The penalty of data URIs for older phones is severe – on Android 2.2 materialization of five 17.8kB images took almost 2 seconds in the data URI condition!
The candlestick plot above also shows that standard deviation is much greater for data URIs. This is a predictable result as the data URI condition will require more CPU power to perform the base64 decoding step, and CPU availability will fluctuate greatly between different handset models and depending on background tasks executing on the phone.
Let’s look a little bit more into this variance using histograms!
Histograms show the frequency at which a given result was obtained.
Here we can see that on recent (Android 4.2, iOS 6) phones, binary materialization happens in the first 2 milliseconds. On the other hand, data URI materialization has a long tail starting at around 7.5 ms for iOS and 10ms on Android 4.2 and tapering up into the hundreds of milliseconds. It’s this tapered tail that skews the mean performance of data URI materialization to 43 ms for Android 4.2 and 33ms for iOS 6.
These histograms suggest that data URI image materialization behavior is much less predictable than binary materialization.
Recommendations & Conclusion
Data URI materialized images are significantly slower than binary materialized images, even on recent smartphones running the latest mobile operating systems.
This doesn’t mean you shouldn’t use the data URI scheme. But it does mean that you should only use them infrequently for small images and consider using a CSS sprite instead.
If you are thinking of using data URIs, consider CSS sprites instead.
They will be more performant in most cases if you can set a far-future expires on your sprite and use a CDN. If your CSS sprites are hosted on the same hostname as your CSS files, the browser will already have a warm connection which will reduce the transfer time. Using a sprite will also decrease the size of your CSS file over using data URIs, as CSS is a blocking request, minimizing the size of this file is a best practice for ensuring your page renders as quickly as possible.
All questions and comments are welcome!