HTTP/2 arrives but sprite sets ain't no dead

In this study, we show that even if the new HTTP/2 protocol significantly enhances page load performance, time has not yet come to totally forget front end optimizations. Today, we will focus on sprite sets.


HTTP/2 became available in 2015, as an alternative to replace the venerable HTTP/1.1, in use since 1997. Many authors [12] foretold the deprecation or even the counter productiveness of front end optimizations. Among those classic optimizations are sprites: the encapsulation of multiple small images (the sprites) within a larger one (the sprite set).

Despite the rapid adoption of this new HTTP standard by both browsers and servers [34], we were not able to find published hard measures to support the claim. As web architects, we were then legitimately wondering whether we should deprecate the sprites pattern or not. As W. Edwards Deming famously quoted it: "In God we trust, all others bring data." We therefore launched our own benchmark.

The first part of this article describes the main differences between HTTP/1.x and 2 and why they could favor sprites deprecation. In the second part, we will then present various results.

Sprite sets

Sprite sets are a front-end optimization. Instead of individually loading multiple images from the server (think about a collection of icons used all over a web page), a unique sprite set is loaded once and individual sprites can later be cropped out. The sprite set used in our benchmark, from www.facebook.com, is displayed in figure 1.

sprite_facebookFigure 1: the benchmark sprite set.

This sprite set is composed of 72 sprites, which can be cut very precisely in small individual image for each sprite.

The first observation is that the sprite set as a single global image is 71 kB, while the sprites as individual images represent a total of 106 kB, an increase of almost 40 %. The union volume is then smaller than the sum, thanks to a better image compression and a reduced image header overhead. Moreover, a single request to the server is sufficient to load whole site icons with a sprite set, instead of multiple requests querying individual images.

In order to render images inside the browser, the sprite set is cropped in small icons through CSS code [6]. In figure 2, you can see the HTML used with or without sprites, and the corresponding CSS codes. The resulting load timelines are also plotted.

CSS with individual imagesHTML (Common)CSS with sprite set
div.sprite-icons {<br> background-repeat: no-repeat;<br>}<br>div.sprite-icons-pause {<br> background-image:<br> url('icon-pause.png');<br> width: 60px;<br> height: 60px;<br>}<br>...<div class="sprite-icons<br> sprite-icons-pause"><br></div><br><div class="sprite-icons<br> sprite-icons-pause-disabled"><br></div><br><div class="sprite-icons<br> sprite-icons-play"><br></div>div.sprite-icons {<br> background-image: url('icons.png');<br> background-repeat: no-repeat;<br>}<br>div.sprite-icons-pause {<br> background-position: 0 -60px;<br> width: 60px;<br> height: 60px;<br>}<br>...

sprites-timeline-withspritessprites-timeline-nosprites

Figure 2: with and without a sprite set. Styling syntax is displayed for both solutions on the upper line. The two images capture http request(s). Without sprite set, one can note the multiple concurrent small requests, with an overall longer completion time.

We can observe how CSS code is much simpler without a sprite set, but the load timeline is way shorter with one. A couple of technical burdens car arise when using set, as they will be discussed below.

However, the main limitations to the sprite set approach are :

  • a more complex development since one has to create or adapt the sprite set and  maintain CSS code to split it in individual icons. This process can nonetheless be automated in the build factory with tools such as glue;
  • a browser cache invalidation at each sprite set modification, even if it is just about a single sprite added, removed or modified.

HTTP/1.x and sprites

Under HTTP/1.x, at most one request is allowed per TCP connection between client and server. Subsequent requests have to wait in order to reuse the TCP connection. In order to get shorter load time and to avoid a page completion to be held back by a single long request, modern browsers open multiple parallel TCP connections with the server (typically between 2 and 8 connections, depending on the browser [5]. However, such a  parallelism is limited and having many requests still means that load time will be longer, network overloaded, without mentioning the backend service.

Loading all independent images at once leads to many serialized requests in HTTP/1.x and strongly impacts the overall loading time, hence the user experience, while using a sprite set results in a single request which is a huge optimization on many websites.

HTTP/2 and sprites

With HTTP/2, all requests between the browser and the server are multiplexed on a single TCP connection [7]. Bypassing the cost of opening and closing multiple connections, this induces a better usage of the TCP connection and limits the impact of client-server latencies.

It could then become possible to load tens of images in parallel on the same TCP connection. As a consequence, using sprite sets to reduce the number of requests could become meaningless. All these sentences are still hypotheses: this article will show how reality is quite different from theory.

Benchmark methodology

All the code to reproduce this benchmark has been made available on GitHub [8].

To reproduce various situations, six HTML pages have been crafted. The first one takes advantage of the sprite set when the later ones incorporate various quantities of individual images.

Setup nameImagesHow many images
Singlesprite set100% (72)
AllSplittedindividual100%
80pSplittedindividual80%
50pSplittedindividual50%
30pSplittedindividual30%
10pSplittedindividual10%

The last four pages, with only a fraction of the sprites, represent a common situation, where all the sprites are not simultaneously used: only an image subset is actually needed, depending on the context (language, geographical location, application state…). By using individual images instead of a sprite set, it would hence allow to load only required images instead of the whole set. But our results will demonstrate how efficient the grouping still remains.

In our benchmark, a JavaScript code has been developed  to measure the timespan between the end of page HTML load (execution of scripts in the page header) and the last image load (last ‘onload’ event). This timespan is measured and recorded for each case. On the server side, these pages and images have been installed on two NGINX 1.9.5 servers located in the same datacenter on two identical virtual machines.

One server is supporting HTTP/2, while the other only supports HTTP/1.1. These pages are requested in HTTPS, even with HTTP/1.1, to allow a fair comparison with HTTP/2 which supports only secured transmission.

On the client side, a Python script has been developed to request pages through two browsers, Firefox 41.0 and Chrome 45.0, driven by Selenium WebDriver [9]. Selenium allows to have a new navigator context at each request, in order to avoid caching effects. Indeed, if images were to be cached by the browser, we would not really test the protocol since there would be no actual transfer (only an empty body reply with 304 code). Selenium finally allows to easily inspect the DOM to retrieve the timespan measured by Javascript and displayed on the page. sprites_protocol

Figure 3: the test code architecture.

In order to gather robust metrics, the protocol is executed a hundred times for each case, as showed in the pseudocode below.

for i = 1 to 100 for page in ('Single', 'AllSplitted', '80pSplitted', '50pSplitted', '30pSplitted', '10pSplitted') for protocol in ('HTTP/1.1', 'HTTP/2') for browser in ('Firefox', 'Chrome') #load page and measure load time

For each case, the median value is recorded. Indeed, when looking at the time distribution of one case (cf. figure 4), we observe outliers, due do the inherently stochastic network process. The average would hence be too heavily influenced by these points. On another hand, the median is a trustworthy indicator since the distribution almost follows a homogenous distribution. sprites-graph1

Figure 4: original loading times, when repeating the measure a hundred times.

To broaden the range of actual situations, the protocol has been repeated on 3 client configurations:

configurationdescriptionavg latencyupload bandwidth
#1VM in well deserved datacenter10ms80Mb/s
#2laptop with good internet connection40ms20Mb/s
#3laptop with poor internet connection35ms1.3Mb/s

Benchmarks results

The three configurations have produced very coherent results, displayed in figure 5. sprites-graph2

Figure 5: median overall loading time for various page setup, browsers, http protocols and network configurations.

One can observe that:

  • the sprite set load time is equal or smaller than the load time of 10% of the individual images, even with a low latency connection. In other configurations, the sprite set is much faster to load than individual sprites, disregarding the used HTTP protocol;
  • HTTP/2 brings a clear decrease of load times compared to HTTP/1.1, but the HTTP protocol enhancement is not sufficient to reduce the front-end optimisations usefulness;
  • for this problem, the browser makes no significant difference (the difference of load times in configuration #1 are probably induced by  CPU and memory constraints on the virtual machine).

To further analyse these results, one can also plot the median load time as a function of the number of requests or as a function of total image volume. Figure 6 displays results for the aforementioned configuration #3. sprites-graph3sprites-graph4

Figure 6: the same experiments as figure 5, with configuration #3, but plotting timings versus number of image and overall image volume.

With these two last figures, one can observe that the sprite set pattern is very different from the individual sprites due to the single request to perform, while the total volume seems to be a second order effect only.

Conclusions

This benchmark clearly advocates that sprite set optimisation is still relevant, even when upgrading to HTTP/2 protocol. Even though this new protocol offers significant load time enhancements (up to 50%) compared to HTTP/1.1, it may not be enough. If HTTP/2 optimizes network usage, it will not be sufficient to totally dismiss  front-end optimisations, among which are sprite sets, CSS and JS minification and bundles.

References

[1] https://mattwilcox.net/web-development/http2-for-front-end-web-developers

[2] http://http2-explained.haxx.se/content/en/part3.html

[3] https://en.wikipedia.org/wiki/HTTP/2

[4] http://w3techs.com/technologies/details/ce-http2/all/all

[5] http://stackoverflow.com/a/985704

[6] http://www.w3schools.com/css/css_image_sprites.asp

[7] http://qnimate.com/what-is-multiplexing-in-http2/

[8] https://github.com/benoit74/http2-sprites/

[9] http://www.seleniumhq.org/