This experiment compares the page load times of two different versions of the social media widgets I include at the bottom of my posts. I was actually running this experiment to see for myself the kind of improvements I could achieve by combining my social media widget icons into a single image. Since I’ve already gone through the trouble of testing these pages, I figure I might as well publish the results.
This experiment has implications for anyone who’s heard about using CSS sprites but is wondering if the page speed benefit is worth the effort. Although my social media icons don’t actually use sprites, the principle is basically the same: combining several small images into one large image can help your web page load faster.
Setup
I created two test pages that render exactly the same in a web browser. The content of both pages appears like this:
However, the two test pages are coded differently. The first test page uses a separate image for each of the 6 social media links. This results in 7 HTTP requests: one for the HTML page itself and one for each of the 6 images. The second test page uses only 1 image, which contains all of the social media icons combined. This results in only 2 HTTP requests: one for the HTML page itself and one for the image. Here is the HTML code I used for each of the test pages:
Six Images
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Six Images</title>
<style type="text/css">
#post_widgets{width:360px; height:80px; overflow:hidden; background:transparent;}
#post_widgets a{display:block; width:60px; height:80px; float:left;}
a img{margin:0; padding:0; border:0;}
</style>
</head>
<body>
<div id="post_widgets">
<a href="http://twitter.com/">
<img width="60" height="80" src="/img/twitter.png" alt="Share this on Twitter!" />
</a>
<a href="http://digg.com/">
<img width="60" height="80" src="/img/digg.png" alt="Digg this!" />
</a>
<a href="http://delicious.com/">
<img width="60" height="80" src="/img/delicious.png" alt="Bookmark this on Delicious!" />
</a>
<a href="http://www.stumbleupon.com/">
<img width="60" height="80" src="/img/stumbleupon.png" alt="Stumble this!" />
</a>
<a href="http://www.facebook.com/">
<img width="60" height="80" src="/img/facebook.png" alt="Share this on Facebook!" />
</a>
<a href="http://www.reddit.com/">
<img width="60" height="80" src="/img/reddit.png" alt="Submit to Reddit!" />
</a>
</div>
</body>
</html>
One Image
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>One Image</title>
<style type="text/css">
#post_widgets{width:360px; height:80px; overflow:hidden; background:url('/img/widgets.png') no-repeat;}
#post_widgets a{display:block; width:60px; height:80px; float:left;}
#post_widgets a span{display:none;}
</style>
</head>
<body>
<div id="post_widgets">
<a href="http://twitter.com/">
<span>Share this on Twitter!</span>
</a>
<a href="http://digg.com/">
<span>Digg this!</span>
</a>
<a href="http://delicious.com/">
<span>Bookmark this on Delicious!</span>
</a>
<a href="http://www.stumbleupon.com/">
<span>Stumble this!</span>
</a>
<a href="http://www.facebook.com/">
<span>Share this on Facebook!</span>
</a>
<a href="http://www.reddit.com/">
<span>Submit to Reddit!</span>
</a>
</div>
</body>
</html>
Procedure
I used this web page speed testing tool to measure the page load times. Each of the 2 pages was tested 10 times. The testing server was located in San Jose, CA, and my content server was in Seattle, WA. The pages were requested using IE7 with 2 parallel connections enabled. I ran the entire experiment twice. In the first experiment, I saved the images as 24-bit PNG files. In the second experiment, I saved them as 8-bit PNG files.
Results
| Test | HTTP Requests | Bytes Downloaded | Download Time |
|---|---|---|---|
| 6 images @ 24-bit PNG | 7 | 16KB | 0.623s |
| 1 image @ 24-bit PNG | 2 | 14KB | 0.541s |
| 6 images @ 8-bit PNG | 7 | 13KB | 0.608s |
| 1 image @ 8-bit PNG | 2 | 7KB | 0.461s |
Conclusion
Using 24-bit PNG files: the page load time for 1 image was 13.2% faster than the page load time for 6 images.
Using 8-bit PNG files: the page load time for 1 image was 24.2% faster than the page load time for 6 images.
{ comment Leave a comment }
Darren thx for the testing and write-up – good to show this post to web devs that don’t want to take the time and do the sprites. Thx for the stats man, cheers
Great right up – so nice to see a simple but thorough test to show the speed benefits of making fewer requests.
Seeing figures like this really justify creating your own social media icons; kinda fed up with those clunky WP plugins that just bloat out web page code. This is a much better option!
Nice one!
However, you might wanna keep your images under 25kb for iPhone users cause iPhone won’t cache bigger files.
Or you could just serve them a mobile version which would be a better option.
lol how ironic after u explain the benefit of sprites u have a share and bookmark section not done using sprites!
How exactly did you come to the conclusion that my “share and bookmark” section doesn’t use sprites?
For your information, it DOES use sprites. But the real irony here is that my use of sprites doesn’t speed up the page load whatsoever. I’d explain further, but judging by your comment above…you’re not smart enough to understand and I’d be wasting my breath.
Thanks for stopping by.
-SEO Mofo
What about losing the ability to add some alt tags, and replace for only one, is it worth the bandwidth save?
yeah but can you put alt text (for best seo) on a div? sprites need a div + CSS background position, but I’ve never seen alt text on anything but HTML img tags….
RE: alt text
You can use a plain text link and then cover it with an empty absolutely-positioned
<span></span>element that contains the sprite as its background image.HTML:
CSS:
a, span { display:block; width:100px; height:100px; } a { position:relative; } span { position:absolute; top:0; left:0; background:url(/sprite.png) [x]px [y]px no-repeat; }Darren — there’s another aspect of this that’s a little harder to quantify: the total number of requests a page makes can create a server-side bottleneck. Here’s why: Apache sets a limit on the maximum number of “child processes” concurrently serving requests in order that it not exhaust server memory.
Each request uses one of those slots — a site with tons of images, even little ones that are optimized will tend to flood the server (even if for a very brief time). In these days of huge PHP processes it’s not uncommon for servers to be configured to max out at 20 or so child processes at once.
One of the sites I work with has gazillions of tiny little images, so a fresh page request results in almost 50 requests for images and css and javascript, etc. (Oy!). This means on a regular page load our server is instantly maxed out meaning asset loading cannot be parallelized after some point. When this happens, the server queues the request and can’t handle the next until the first ones are returned.
This is very easily observed using tools.pingdom.com — all the “green” you may see is the time that the client is waiting for the server to get unblocked so it can process the request. I think this is pretty prevalent — check out www.seomofo.com or other sites on tools.pingdom.com — interested in what you think.
If what pingdom shows is correct, the total number of requests made (to a given server) could be significant indeed. One of the reasons PageSpeed and YSlow suggest hosting images on a content delivery network is to offload this low-value work of serving static resources. I am using Amazon S3 for images on my blog (and may move to Cloudfront), and it seems to help keep my server load and connections down.
First of all, actual web browsers that real people use do not request 50 images at once, and therefore do not take up the server resources of serving 50 simultaneous images! They typically only open about 6 connections to the server, and request page assets both serially and in parallel (i.e. feeding the 50 through the 6 concurrent connections, requesting them 6 at a time). So web servers will only use 6 processes or threads for that user.
Second of all, all modern web servers and browsers support this feature called “Keep Alive” (if you enable it on your web server, many have it turned off by default!) which dramatically increase the speed at which serialized downloads work… perhaps even almost to the point of being very close to combining images into one image. I say “almost” because there is a tiny bit of overhead, in the sense that there are a few extra HTTP headers, but these are usually very small compared to the content being served. This overhead can be worth it in certain circumstances such as when the full list of images aren’t actually used on every page, just some here and some there.
In addition, if there’s any processing going on for generating the images on the server side, and if your server has multiple CPUs or cores… then it might actually serve those images /faster/ by dividing them up among multiple concurrent connections, instead of trying to generate a larger one and serve it all at once through a single connection. I suspect this is likely a negligible effect for static images of course, this is more for dynamically generated ones that actually take processing power to serve.
And there’s also the issue of compression, to speed things up. Typically this isn’t an issue for images, since most image formats support compression natively, and most image creation software automatically use it. However, for text elements of the page like HTML, JavaScript, CSS, etc… page loading speed can be dramatically increased by enabling it on the server for such non-pre-compressed file types. All modern web browsers support it, and so do all modern web servers, but very few web servers have it enabled or configured, and none do by default. This is probably because it does increase CPU usage on the server a little to do the compression. But unless your servers are close to full capacity (and you should be looking to upgrade soon then anyway), I highly recommend enabling it on your server. Typically the uber cheap five-dollar-a-month shared web hosting services that you don’t control your own server will not enable it for you, even if you ask, so you’ll have to be running your own machine or virtual machine to use this (like twenty a month and some expertise).
The list goes on and on, including caching, and many other things that can be done to increase (or decrease) speed. But I’d say the main point people should take away from this is that there’s a lot about your server hardware, configuration, and networking setup that can help or hinder your site speed, not just website design. Everything works together (or against each other) combining to present the best (or worse) experience for the end user.
Dave — I assume your response is directed at my reply.
In short, yes: I agree. No web browser would serially request 50 images, but it’s quite easy to see that the first 5 or so get pushed back on the first thread (which is seen as a connection on the web server), and the next batch on the next connection and so on. Having 50 discrete asset requests may result in 5 to 10 discrete connections, keep-alive included. And this number of connections can quite quickly max out a small Apache config with MaxClient/MaxConnections set to 15, for example. All you need is two or three users hitting the site at once. All the subsequent requests, in my example are queued, waiting for the next available Apache client.
So all of this can be mitigated with small images, server and client caching, sprites, minify, gzip, and so on. Every little bit helps.
But a sucky page and site design, especially the over-use of little images that are not combined as sprites ultimately forces some web server configurations to a bottleneck (queued requests, waiting for the next client) state quickly. The site I am thinking about uses Drupal, and its design was done by novices who didn’t realize that, for example, a page showing Twitter, Facebook, Google, RSS and other icons in three places should … all used the same image!
I spend much of my time lately looking at sites generated by CMSs that people don’t know how to use. The scenario I describe is far from common and can be easily observed in Firebug, Pingdom, Pagetest and various other tools that show what actually happens in real life.
Try them out on some random sites. I think you’ll quickly find a bunch like the ones I am doing my best to fix. Eventually, you need to get the page design fixed.
It is indeed all inter-connected!
Tom
hehe, Tom, of course only the “50 images” example was directed at you. You’re right that 3 people simultaneously opening 6 connections (using my example for a browser) would more than max out a 15 connection server maximum (using your example for a very small server configuration), making some connections start to wait a bit for a free one.
The rest of my post was more a rant at the issue in general. It frustrates me when I see some people so focused at reducing the number of connections to increase speed, that they forget about other things that might be a lot better for them depending on the situation.
For example, moving ALL images into one giant sprite image, including those not even used on a given page wastes bandwidth and slows it down serving stuff that page doesn’t need, compared to breaking them up a bit more and relying on keepalive instead. Or better yet, use both: carefully combine images just where it really does boost them (i.e. when they’re always displayed on the same page), and use keepalive and real concurrency for others where that would benefit them better. People just need to think of all the issues together, and not just focus on one, that’s all.
I see this same issue with a lot of the javascript libraries out there… hey, I have an idea, let’s include a giant library on the first hit, when the first hit only needs about 2% of the file, and the rest of my site maybe only uses 10% of the file…. or…. we could modularize it a bit to save a lot of bandwidth since we don’t need it all, and pages that do use more of it just request more of it in separate requests and use keepalives to get nearly the same type and degree of boost as that giant conglomeration into one file does…
But I can understand if a lot of people have no control over their web servers or no knowledge on how to optimize them if they did, that’s just not the optimal way. The optimal way is to fine tune every part of the process together.
Dave — I’m with you, man.
The secret to optimizing performance is, well, understanding what’s causing the performance problem in the first place. At some level, I appreciate PageSpeed and YSlow for even raising these issues, but of course people tend to blindly follow their directions without actually diagnosing root cause.
My 50 image example is a Drupal site running on a 2GB VPS instance, with MySQL on, too. Drupal is such a pig that a typical client can run up to 30MB — you never know which module is going to decide that it needs to put something massive in memory. Coming from a C/C++ background, this is stunning to me. So even in an environment where we have control (sort of) the site will be down if we set MaxClients higher than around 20 (default for Apache is 120, I think). So in that case, clearing connections fast is the best thing we can do.
I had a friend with a really nasty WP theme, and even worse host (Network Solutions, if you must know). The WP theme would break when javascript was minified, had unoptimized images, etc, etc. The site took, at times, 15 to 30 seconds to load. No, really, it was horrific. So I did all the stuff I could (gzip, browser cache, file-cache, combine, minify, compress images, etc) and got things from abysmal up to really bad. Now the page loads completely in a blazing 5 seconds. Woo-hoo: 3x to 6x performance increase! And it’s still terrible.
In the end, the host is the problem — it’s a shared host, so no access to apache config at all. So if they are overloaded, which they usually are, all our site gets a little slice of the CPU and connection every so often, and then waits. I can load the same page on my development VPS slice, which is actually kind of on the edge itself in under a second.
So all the nice stuff I did for the site only puts lipstick on a pig.
I have been answering questions in the WebmasterWorld apache forum for the last year or two. At first, I made all these assumptions about things people could do, since I never had to deal with a shared host. But it seems like most sites are actually hosted in this kind of environment. People are jumping through massive hoops and saying “My site is down, I am losing money every minute” and when I say “For $20 a month you could get a VPS” they say, “Oh no, that’s too expensive”.
Sigh
Hey Tom,
I just remembered that I had started to respond to your first comment awhile ago, but then got sidetracked and forgot about it. I’m just going to post it as-is:
Thank you for commenting. You mentioned a few things that I’d like to clarify/embellish:
If we’re talking about requests from a single page, then it’s highly unlikely that any reasonably-configured Apache server would struggle at all. Most browsers won’t even attempt to open more than 2-6 connections at a time, so even if a page needs to load 1,000 images from your server…it would still only request a couple at a time. Your server would NOT be hit with all 1,000 requests at once.
A more-accurate wording would be “Apache has a configurable limit…”
The default Apache configuration is not ideal for every website, but it can be tuned to suit your site’s specific needs (usually by editing the httpd.conf file and/or .htaccess files).
If you’re using Apache 2.*, you have the option of using different MPMs (Multi-Processing Modules). The default is prefork, which uses a child process for each request–as you’ve mentioned. Alternatively, you could install worker, which supports multi-threaded response handling per child process. If your server is bottlenecked by memory, you may want to try worker instead of prefork.
I don’t manage enough servers to be able to comment on trends, but I will say that I’ve personally never seen Apache configured that low. The default value is 256.
Thanks for the reply. Yeah, to be sure, the scenario I present isn’t ideal. But I was actually pretty surprised to find how common it is. Drupal recommends setting php memory limits to 32MB, or larger if your site uses image processing libraries. So if each process (or thread) uses 32MB, and in the cases I have seen, they do, 20 separate apache clients will use 1GB of RAM.
It’s outrageous, actually! So in a case where you’re running the OS, DB, and putting some pages in memory cache, on a 32-bit OS, you’re stuck with a remarkably small number of apache clients. We have tried both worker and prefork — in the end, worker is more efficient, but the overhead of starting and running a process is trivial compared to the memory used by Drupal.
So everything you guys are saying is completely right … assuming you have plenty of memory and small processes.
But things definitely can max out with even a modest number of simultaneous requests. If the site is getting crawled by multiple bots, and you’re in a high user-traffic situation, it doesn’t take much to start hitting limits. And as soon as the server gets a little bogged down, people start hitting the refresh button, leaving a stranded connection that only gets closed after the timeout is hit (default: 300 seconds, best to set at more like 10!).
So back to the main idea, when there’s a constraint such as the one I describe, there’s a significant benefit to combining images, combining multiple JS libraries and CSS files, minify/gzip, etc (even if you’re pushing more bits than needed). And then, serve static resources (images, css, js) from a CDN.
Finally, it’s probably worth scoping around on the web — the vast majority of sites are served on shared hosting environments. This was a surprise to me, since I have always worked on servers I can configure and tune myself — on shared hosting what you can do in .htaccess and php.ini is incredibly limited. Pingdom.com and PageTest make real requests to real sites using real browsers. Try out smaller online stores, small organizations, etc. and you’ll immediately see: what I am describing is surprisingly common.
I’ve found in my own experimentation that using “event” mpm sometimes works really well too. It’s still marked as “experimental” but it appears to me to work fine under linux (not always as good compatibility under my mac osx development environment though, I had to go back to “worker” there).
It operates almost the same as “worker” but it allows me to crank up the keepalive timeout to say, 5 or 10 minutes, without tying up actual worker threads/processes waiting that long. This makes subsequent page clicks in the same browsing session blazingly fast and responsive, because the connections are likely already still open, if you are clicking around the site…
Experimentation is key often, just try it with different variables and see what happens. Measure the results. For example, on one Ruby on Rails project of mine, I found that I had to crank the backend ruby processes (which are separate from apache processes, unlike mod_php) down to just 2, for some heavy image processing it was doing, because it was using too much memory. And when it started reaching limits, waiting for a connection measured significantly speedier than making the server swap a lot. Keep in mind the server only has 4 cores too, so it’s pointless to run very many more than that many cpu-intensive things at once anyway, regardless of memory limits. Although I could keep the apache server serving a couple hundred threads of (non-cpu-intensive) static content at once on the same server using a small fraction of the memory… This was on a 512 meg VPS that was running mysql too, so when I say “using too much memory” I’m not really talking that much… If the site starts getting much more traffic, it will obviously need to be upgraded… It’s monitored, don’t worry :)
Another important thing to find out when experimenting is, where is the limit? Hammer on it hard enough to bring the server to its knees, then set the max config variables to be just a bit below where it starts doing that… When managing the site correctly, traffic will always be increasing over time, so we can assume we’ll all need to upgrade periodically. We’ll need to know the limits so we can do it before people notice much of a slowdown.
I got to say, once I tasted VPS a couple years ago I will never go back to shared hosting for anything of my own, not even if it were free… :)
Fantastic test – but (prepare for stupid question) – still lost on the nuts and bolts of how it’s done, exactly. I’m not a coder in the least bit, but want to learn how to combine images in one file using CSS sprites, my pages are sllllllooooow and it’s killing me.
I’ll bookmark this and take a third and fourth look at your code samples, I just feel entirely out of my element messing with it. Found you on SEOBullshit – lost an otherwise productive day trolling those posts…wow. Seriously good stuff, but I’m 10 miles behind in understanding the coding aspect.
If you’re struggling with the code, try this free tool. It will try to generate CSS sprites for you. Results vary, but at the very least, it will produce examples for you to look at.
CSS Sprites Tool