Data URIs for CSS Images: More Tests, More Questions

data-uri-css-projectionToday we’re going to take another look at embedding data URIs in stylesheets.  For this example I’ve taken a free HTML/CSS template from Smashing Magazine and created 3 versions of a page.

1. The original. It consists of one HTML file, one CSS, one JS, and 31 images. Total HTTP requests: 34. Total size: 388KB.

2. Embedded data URIs #1 (Strict). In this version I used CSSEmbed to convert all 31 CSS images into data URIs. The data URIs were embedded directly in the main stylesheet. Note that 2 of these images exceed the 32KB limit imposed by IE8 on dataURIs. Total HTTP requests: 3. Total size: 365KB.

3. Embedded data URIs #2 (Loose). For this version I used duris.ru to separate the data URI image declarations from the main stylesheet. All of the data URIs are placed in a separate stylesheet, called in the footer, directly after the jquery reference. Placing the second stylesheet just after the external script in the footer forces the browser to render progressively, rendering the styles in the main stylesheet as soon as it’s received. In this version I’ve also kept the two 32KB+ images as regular image references in the main stylesheet. Total HTTP requests: 6. Total size: 374KB.

Predictions?

In the strict data URI scenario we have 31 fewer HTTP requests than the original and the total size of the load is actually 23 KB smaller.  According to Steve Souders, each HTTP request adds ~200ms to the load time (on worldwide average). So, the math says we should expect a serious performance improvement.

Results

(I was feeling a little burnt out on browser testing from the previous post about background images, so for now I’ve only recorded data for Firefox 3.5, empty cache.)

Original:

data-uri-org

View Full-size

Average total network time (via HttpWatch): 1.35s.
Average total network time (via Hammerhead): 1.42s.

Embedded data URIs #1 (Strict):

data-uri-test1

View Full-size

Average total network time (via HttpWatch): 1.13s.
Average total network time (via Hammerhead): 1.27s.

Embedded data URIs #2 (Loose)

data-uri-test2

View Full-size

Average total network time (via HttpWatch): 1.13s.
Average total network time (via Hammerhead): 1.02s.

In the end

So we do end up a little bit faster, but not as much as we might expect after reducing our HTTP requests by 91%. Note that even though the two data URI versions are very similar in load time, the second feels faster because of the progressive rendering.

What do you think?  Are these savings enough to justify the extra effort?  Don’t forget that we still need to take further steps to handle IE6/7.  Is there something about this particular page that makes the results atypical?

If you want to have some fun with the test pages yourself, here are the links: Original, Embedded data URIs #1, and Embedded data URIs #2.

Update

After further inspection I’ve noticed that the original stylesheet contained a few redundant background image declarations on hovered elements, which was adding a tiny amount of extra weight to the data URI stylesheets.  For the sake of cleanness I’ve removed them from the test pages (a few extra tests showed they had no significant impact on the results).

But this highlights an important difference between data URIs and normal images in stylesheets: with data URIs, all image data in the stylesheet is downloaded when the stylesheet is downloaded; with regular images, images are only downloaded if/when they’re referenced in the HTML.

Update #2

In the comments Barry Kukkuk raised a great point about latency. Latency is definitely a major factor and will be the focus of my next round of tests. Barry and a few others were kind enough to report back with timings from their locations. (My server, btw, is located in Texas, US.)  Thanks guys!

South Africa: 4.04s, 1.44s, 1.92s
Ukraine: 4.8s, 4.3s, 2.6s
Czech Republic: 7.31s, 3.63s, 3.45s
New Zealand: 3.38s, 2.69s, 2.85s

Both comments and pings are currently closed.

Discussion

I’ve been using datauri’s on http://kennethreitz.com for a little while now. I’ve been pleased with the results.

Using datacompression is significantly helped as well.

I live in South Africa, and the latency to North America and Europe is pretty big. These techniques make a *huge* difference:
Here are my times (as per Firebug’s Net timings):
Original: 4.04s
Embedded #1: 1.44s
Embedded #2: 1.92s

And I must agree: The last one also feels quicker, because of the progressive loading.

You save the network round trip but the image still takes time to be “loaded” (aka downloading of the initial html page and then parsing of the datauris). I just took a look at speed tracer in chrome, you can compare the waterfalls from original and datauri page to see that.

See: https://twitpic.com/t15ai/full

You can see on orignal, the initial html download is very fast and on data uri #1 it takes much longer.

Then, you have parsing of each data uris.

I wonder where that 6 seconds didn’t go to (31 x 200ms)… I assume you have a fast connection where the save in parallel requests & network latency is perhaps less significant.

But on mobile, say, this technique might have a far greater % effect. Assuming, that is, that most mobile browsers have a clue about data URIs in the first place (which I’m skeptical about).

@Barry
That’s great information. That’s exactly the kind of feedback I was hoping someone would reply with. I’m going to add these numbers to the post once I get a few more user responses. Thanks!

@JP
Thanks for the Speed Tracer results. I’m looking forward to playing around with the pages in Speed Tracer later this weekend.

I’m not sure why the html download would take that much longer in the data URI versions. I’ll have to check that out. Aside from that and the difference in the intensity of the network resources, the timing looks about the same. I’m not sure what the 17.82s number refers to in the original. I think that may have been affected by a rollover image that was triggered. (I’ll remove that from the test page.)

@James
Yes, I suppose the connection I tested on was a little fast. =) And FF’s 6 parallel downloads does mitigate the the problem of many HTTP requests. A greater difference may be seen on Safari/Chrome with their max of 4 downloads.

And mobile brings up another good point, although I think you’re correct that mobile browser support for data URIs is probably very sketchy.

blackberry, iphone, android and pre support data-uri’s, so in terms of users you should care about i think you’re covered.

Isn’t it comparing apples to oranges? Shouldn’t the datauri’s be compared to spriting to get a more accurate comparison? Just a thought.

There are a couple of things I’d suggest as you continue your research. First, latency is a big issue with HTTP requests as Barry pointed out. To get a good baseline, you’d need to test when the server is local and when it’s a couple of timezones away to see what the normal latency on broadband would be. I’d also suggest throttling your connection speed (if you’re on Windows, you can use Fiddler to simulate modem speeds).

Second, if you’re loading the images from the same domain, you may want to try loading them from a different one as DNS resolution also takes up some time.

Looking forward to more. :)

I’m on a very slow wireless connection. Using firebug I got 7.61, 4.13 and 5.31 but as Barry Kukkuk said, the last one seems faster to me; because I know what I’m talking about, I am going to rephrase that: the last one seems more responsive ;)

original ~ 4.8 s
http://fullajax.ru/temp/datauri/ravelrumba/test2/datauri-0.png

datauri #1 ~ 4.3 s
http://fullajax.ru/temp/datauri/ravelrumba/test2/datauri-1.png

datauri #2 ~ 2.6 s
http://fullajax.ru/temp/datauri/ravelrumba/test2/datauri-2.png

very important: DOM ready event in datauri #2 much faster than datauri #1 and almost equally with original

Hello from czech repbulic! I have worse times that from south africa? (16 Mb fiber) Uhhh…
7:31
3:63
3:45

To everyone that submitted your timing results, thanks! I’ve added them to the post.

@Subhash
The intent was to examine the extreme ends of the spectrum (a fully unoptimized page with 34 requests and a super-optimized page with 3 requests) and to show that even then the outcomes were surprisingly not far apart. Using sprites would put us somewhere in the middle of the spectrum. But I agree that eventually we’ll also want to examine how data URI images compare to sprites.

@Nicholas
Thanks for the feedback. I’ll keep all of that in mind when I get around to the next set of tests.

@Ruslan
Thanks for highlighting the important difference in the timing of the DOM ready and onload events.

New Zealand: 3.38, 2.69, 2.85 (averaged over three runs each, clean cache).

Scott Schiller

Interesting stuff. Another item that may help – I’m not sure if it was included here – is gzip/deflate, for the external CSS with data: URIs. That stuff should compress fairly nicely, and may get you some benefit on load times.
(Related: http://www.phpied.com/data-uris-mhtml-ie7-win7-vista-blues/ )

@Scott – Good point. I neglected to mention it, but yes, all of the stylesheets were gzipped. base64 encoding increases the size of the data by about 33% so without compression we’d be in rough shape.

Unless I’m wrong about this, images for CSS3 border-image tag can’t be combined (‘meta-sprited’ if you will) which strikes me as a huge problem for latency (e.g. my border-image button has 3 states, that’s 3 requests). Seems to me Data Uris are the only answer, so thank you very much for these studies.

Great article!

Unless all your resources load sequentially, you can’t take the 200ms stat and multiply it by the # of requests, due to parallel connections – as @James points out.

Do not load stylesheets in the footer. You’re not seeing the pain of doing this because you’re only looking in Firefox. Firefox charges ahead and renders the page before all stylesheets are downloaded. Chrome, Safari, and IE do the opposite – rendering is blocked until all stylesheets arrive at the browser.

Sprites and data: URIs are better than a bunch of separate background images. The choice between those two comes down to IE 6/7 support. Sites that have enough developer time to handle different implementations can choose the data: URI & MHTML path. Otherwise, sprites is the path – they’re more work to build & maintain, but they work across all browsers. Splitting images across two stylesheets will actually delay rendering in Chrome, Safari, & IE. So, if you’re doing the data: URI path, I recommend embedding the images in one stylesheet in the HEAD.

@ Steve
Thanks for the feedback! I’m a big fan of your work. Regarding the stylesheets in the footer, some tests I did recently indicate that FF, Safari, Chrome, and IE7 do not block rendering if there is an external javascript in between the stylesheet in the head and the stylesheet in the footer. Here’s the test page and here’s the post about it (Scenario #3). Do you have any thoughts on this?

@Rob: Yes, that’s true in FF, Safari, and Chrome. And it’s probably pretty likely that there’s a script between the stylesheets in the header and footer. But it’s one more thing that will go wrong. Also, in IE7 it still blocks the entire page. (That’s what I see in your test page, but I made sure to clear the cache. This Cuzillion page makes it easier to see – http://stevesouders.com/cuzillion/?c0=hc1hfff0_0_f&c1=hb0hfff0_0_f&c2=bi1hfff0_0_f&c3=bc2hfff5_0_f ).

Finally, rather than load my background images last, after everything else, I’d rather load them in parallel. Putting them in a stylesheet at the end means the user will have to wait for everything to download before they see the page I designed. I’d prefer the page to render more evenly.

@Steve Souders
> And it’s probably pretty likely that there’s a script between the stylesheets in the header and footer.

It’s true. See test pages

with JS:
http://fullajax.ru/temp/datauri/ravelrumba/test_footer.htm
without JS:
http://fullajax.ru/temp/datauri/ravelrumba/test_footer_without_js.htm

> Putting them in a stylesheet at the end means the user will have to wait for everything to download before they see the page I designed.

Putting them in a stylesheet at the HEAD means the user will have to see WHITE SCREEN and wait for everything to download.

Stalemate. We need to find a better solution. Maybe, usage js-css bridge will help. What you think?

If you still looking for statistics, here are mine (.har format) (UK)

http://wth.me/labs/orig – 4.43s
http://wth.me/labs/uri1 – 4.34s
http://wth.me/labs/uri2 – 3.57s

You can open it here:
http://www.softwareishard.com/har/viewer/

@Steve
I completely understand and agree that as a rule we want stylesheets in the header. But I wonder if this particular case with data URI images is an exception. Putting the embedded data URIs in the HEAD means everything else is blocked while the heavy stylesheet is d/led. This prevents background images too large to serve as data URIs from being kicked off until the data URI stylesheet has downloaded. A previous test I did showed that this blocking scenario can easily result in significant delays.

Isolating the data URIs in a footer stylesheet allows the regular background images to d/l in parallel with the data URIs. You could also argue it results in a more responsive page.

As Ruslan said, without a better solution it looks like a stalemate.

Again, thanks for taking the time to stop by and offer your thoughts.

In addition, I think, data-uri css can be separate into several small files and then will be available to parallel downloads.

Ruslan that would defeat the whole purpose of using Data URI’s in the first place. Most people use them to be able to eliminate the amount requests the page makes.

When separate the CSS in multiple files you are creating more requests again.

What you probably could do is spread the rest of your content over multiple servers to maximize the parallel connections for those.

Brice Burgess

Another point to make is that there is *always* lower server overhead when using Data URIs. Apache and other web-servers are limited in the amount of connections they can serve at any given time, and the less requests a single visitor makes increases the amount of visitors a site can handle. The actual overhead savings may not be significant in most cases, but ought to greatly reduce resource usage on *heavily* visited sites.

Updating the graphic to show introduced (20ms?) average latency will make the article more clear on first read.

Great post!

Nice test; simple!

A thing that I thought was interesting was that with cache, case #2 (Strict) loads increadibly much fast – when measuring the requests.
I ran the refresh (that doesn’t clear the cache) a few times to get somewhat average results.

Original: 1.26s
Strict: 322ms
Loose: 510ms

Since in the original, I have to check your 30-something objects, and whereas in the Strict mode I just need to check the style.css, and see that it has not been modified (Status 304).

So in my case, where the user is most likely to visit multiple pages, this is a great increase in performance – at least as far as numbers go.

Erik Frister

Hey, just wanted to let you know that Version 3 (Loose), where you embed the URIs into a separated stylesheet in the body tag, does not validate with the W3C Validator. Any solution to that? I agree that it improves perceived performance, but not having it validate seems a big deal…or am I mistaken?

@Erik
Right, placing a link element outside the head will trip a validation error. I think when experimenting with newer web technologies we’re occasionally going to rub the validator the wrong way. =) I’m not recommending that people start putting stylesheets in the footer, but I thought it was very interesting that doing so had a major (and in some ways, positive) effect on how most of the browsers rendered the page.

For my two cents on validation in general, I think the principles of validation are very important. But I’m not sure it’s important that every page validates.

Erik Frister

@Rob
Thanks for your feedback. I like the perceived speed, but having invalid HTML is (at least in my case) not acceptable. Invalid CSS is another case, but HTML…I still love the idea (and it was only after I implemented it that I figured out it doesn’t validate – feels like a loss to merge it back into the head)..

@Erik
I haven’t tested this, but if you keep the two stylesheets separate (as in the loose version) but place them both in the HEAD, it should “feel” faster than the strict version. It will still block the downloading of any non data uri images, so it won’t feel quite as fast as the loose version, but it should still give you the progressive page rendering. And it will validate. I may do some tests on this on later… Let me know if it works for you.

“The intent was to examine the extreme ends of the spectrum (a fully unoptimized page with 34 requests and a super-optimized page with 3 requests) ”

What about an optimized CSS/markup without data URI’s that could also reduce the download time significantly.

The initial latencies found from South Africa and subsequent speed increases may also be due to cached routing from the client to the server. The first hit has to figure out the route, subsequent (even if you clear your browsers cache) do not as the route is stored in the DNS Cache not the Browser cache. Might be best to rerun the tests flushing the DNS cache each time to ensure a fair result.