Insouciant

William Chan's blag

Throttling Subresources Before First Paint

In my last post about resource prioritization, I mentioned that WebKit actually holds back from issuing subresources that can’t block the parser before first paint. Well, that’s true, except for the Chromium port, because recently we decided to disable that. You may be wondering, why would we do that?

Well, we’re doing it because the rendering engine is not the best place to be making resource scheduling decisions. The browser has access to more information than the renderer does. For example, if the origin server supports SPDY, then it’s better to simply issue all the requests. Or maybe the tab is in the background, so we should be de-prioritizing or outright suppressing the tab’s resource requests in order to prevent contention. Or maybe given the amount of contention, we should start the hostname resolution or preconnect a socket, rather than doing a full resource requests.

Therefore, we’re disabling WebKit’s various resource loading throttles in order to get all the resource requests to the browser and have it handle resource scheduling. We haven’t re-implemented WebKit’s mechanisms yet browser side, so currently we have a situation where we aren’t throttling subresources before first paint. So if you’re running a Chromium build after r157740 (currently, only Google Chrome dev and canary channels), then your browser isn’t throttling subresources before first paint. It’s interesting to see what effects this has on page load time.

For example, for www.gap.com, we can compare page loads on Chrome 22 (stable) vs Chrome 24 (dev) using WebPageTest. In this case, we see that the onload and speed index are both worse in Chrome 24. The speed index is clearly worse because the first paint occurs way earlier for Chrome 22 than for Chrome 24. Looking a bit more closely at the waterfalls, that seems to be due to the globalOptimized.js taking longer on Chrome 24 than on Chrome 22. Poking into the code, we see the document is synchronously loading that script, thereby blocking parsing and slowing down both the first paint and the DOMContentLoaded event. The DOMContentLoaded event is key here, since it appears to trigger a number of other resource requests, so slowing down the DOMContentLoaded event also slows down onload. Examining the connection view, we see that the reason that globalOptimized.js (264.4 KB) takes longer to download is due to increased bandwidth contention from image resources.

gap.com waterfall, connection, bandwidth for chrome 22
gap.com waterfall
gap.com waterfall, connection, bandwidth on chrome 24
gap.com waterfall

Examining another example (cvs.com), Chrome 22 again has better first paint / speed index scores, but Chrome 24 has a shorter onload time. Diving into the waterfalls again, we can see that the reason Chrome 22 again has shorter first paint times is because it gets the stylesheets sooner, and stylesheets block first paint in order to prevent FOUC. What’s interesting about this case is that there’s no real bandwidth contention this time, since the last 3 stylesheets only add up to around 7KB. The contention is actually on the connections per host (limit is 6), since the images are requested first, and until some complete, the stylesheets (higher priority resources) requests cannot begin. However, unlike the gap.com case, there isn’t a period of low bandwidth utilization due to waiting for resources to be requested during the DOMContentLoaded event, so issuing the subresource requests earlier results in overall better bandwidth utilization, and thus reaching onload sooner.

cvs.com waterfall, connection, bandwidth for chrome 22
cvs.com waterfall
cvs.com waterfall, connection, bandwidth on chrome 24
cvs.com waterfall

Yeah, there are cases where this change worsens the experience, and cases where it actually improves the user experience. In the real world though, what does it usually do? Pat Meenan ran a test of Chrome stable vs Chrome canary for us awhile back to check it out on a bunch of websites, and in aggregate, we saw a minor improvement in onload times with the new behavior, but a hit on the speed index and a significant hit on first paint time. Therefore, we’re calling this a regression and will either fix or revert the change by the time we hit code complete for Chrome 24.

Update: Thanks to Steve for editorial suggestions and teaching me how to compare tests on WebPageTest :)

Comments