Thursday, April 16, 2015

HTTP/2 is much faster than SPDY thanks to dependency-based prioritization

Background

HTTP/2 provides two methods to prioritize streams (e.g. files being served).

One method is called weight-based prioritization. In weight-based prioritization, every stream is given a weight, and the value is used by the server to proportionally distribute the bandwidth between the streams.

The other method is dependency-based prioritization. By using the method, web browsers can advise HTTP/2 servers to send the streams that are depended by other streams before sending the other streams. For example, by using dependency-based prioritization web browsers can request the server to send CSS or JavaScript files before sending HTML or image files.

As of this writing, out of the two popular web browsers that implement HTTP/2, only Firefox uses dependency-based prioritization.

Note: the background section has been rewritten due to the fact that the comparison with SPDY turns out to be wrong, as pointed out in the comment. The original version of the section is here. Rest of the blogpost stays in tact.

The case of Mozilla Firefox

The prioritization strategy of Firefox is as follows1:
  • send CSS and JavaScript files in <HEAD> before HTML and/or image files by using dependency-based prioritization
  • HTML streams are given 2.5x bandwidth above image streams (by using weight-based prioritization)
  • script files within <BODY> are in total given about the half bandwidths of the other files2

Below is the network time chart generated by Firefox 37.0.1 when accessing a sample web page3 (given 100ms network latency6) containing a number of CSS, script files, and images. H2O version 1.2.0 was used as the HTTP/2 server for running the benchmark.


By looking at the chart, you can see that many CSS, script files and images are requested in parallel at some time around 320ms, but that the download of files that block rendering (e.g. CSS and script files) complete before any of the images (even the smallest ones) become available. This is due to the fact, as I explained earlier, that Firefox notifies the HTTP/2 server that HTML and image files depend on the CSS and the script files to become rendered; therefore the server is sending the files being depended before sending the dependents. And thanks to the prioritization, all the files that are required to do the initial rendering arrives at the web browser (a.k.a. first-paint time) at around 1.0 seconds from start.

The case of Google Chrome

On the other hand Chrome's prioritization logic only uses the weight-based prioritization; the logic remains mostly same with that used in SPDY. Chrome assigns a predefined weight to each of the stream based on their types.

Table 1. Priority weight values used by Google Chrome4

TypeWeight
HTML256
CSS220
script183
image110

And here is the timing chart taken using Chrome (version 44.0.2371.0 canary) when accessing the same web page.


Unlike Firefox, CSS and script files are not arriving before the image files. If you look carefully, you will find a vague relationship between the size and the arrive time of the contents independent to their types. This is because each of the files are interleaved into a single TCP stream based on their weights, and because of the fact that the weight between the files do not differ much. Therefore the initial-paint time is as late as 1.5 seconds5.

Conclusion

As shown, dependency-based prioritization introduced in HTTP/2 brings non-negligible benefit in terms of web-site performance. In case of the benchmark, Firefox using dependency-based prioritization was 1.5x faster than Google Chrome only using weight-based prioritization when comparing the first-print timings.

My understanding is that the developers of Chrome is aware of this issue, so hopefully it will be fixed soon. I also hope that other web browser vendors will utilize dependency-based prioritization. As shown, it is clearly the way to go!



PS. And it should also be worth noting that HTTP/2 servers should implement the prioritization logics correctly. In case of H2O, the server both of the prioritization logics are fully implemented using a per-frame weight-based round robin with the frame size of 16Kbytes at maximum.

note 1: ref: HTTP/2 Dependency Prioritization in Firefox 37, Http2Stream.cpp line 1088 of Firefox
note 2: ref: nsScriptLodare.cpp line 306; I am not sure if this is the intended behavior, IMO script tags in BODY should given a priority equiv. to HTML or image files, and it might be the case that the condition of the if statement should be reversed.
note 3: http2rulez.com was used for testing the load speed, with the <script> tags at the end of the document moved into <HEAD>
note 4: ref: MapPriorityToWeight function of Chrome
note 5: the network chart of Chrome includes a 0.2 second block before initianting the TCP connection, which has been subtracted from the numbers written in this blog text.
note 6: An Ubuntu 14.04 instance running on VMware Fusion 7.1.1 on top of OS X 10.9.5 was used for running the server. Network latency was given using tc qdisc command. Both the web browsers were run directly on OS X 10.9.5.

Tuesday, April 14, 2015

H2O version 1.2.0 released; bundles LibreSSL by default

This is the release announcement of H2O version 1.2.0. Full list of changes can be found in the Changes. The release includes a fix for a heap-overrun vulnerability in the proxy module; users of prior versions using the H2O as a reverse proxy are urged to upgrade to 1.2.0.

Aside from the bug fixes, we have adjusted the code-base so that no external dependencies would be required when building the standalone server.

One of the hustles while trying to install the older versions of H2O (or any other HTTP/2 server) was that it required the newest version of OpenSSL (version 1.0.2). This is because ALPN, a feature that became only available in version 1.0.2 is essential for the HTTP/2 protocol.

However it is often difficult to upgrade OpenSSL on existing systems, since it is used by other important softwares as well (SSH, etc.).

In H2O version 1.2.0, we have chosen to bundle LibreSSL. LibreSSL is not only considered more stable than OpenSSL; it also support new cipher-suites like chacha20-poly1305, which is the preferred cipher suite of Chrome for Android.

If CMake (the build tool used by H2O) does not detect OpenSSL version 1.0.2 or above, it would instruct the build chain to use LibreSSL being bundled. To enforce the use of libressl being bundled, pass -DWITH_BUNDLED_SSL=on as an argument to CMake (note: you might need to clear the build directory before running cmake). Or set -DWITH_BUNDLED_SSL=off to explicitly disable the use of libressl.

Version 1.2.0 also bundles other dependencies as well, so that the server can be installed as simply as by running cmake, make, and make install.

Have fun!