articles

Performance improvement on front-end generated by rustdoc

We recently wrote a blog post about the performance improvement in rustdoc (the "back-end"). Now, it's time to talk about performance in the "front-end". As you will see, there is also a lot of things to say.

Content size vs performance

Since always, a big "fight" has been taking place in rustdoc between the page rendering speed and the amount of content we want to provide. It reached its highest when the Trait impl show docs pull request which added documentation from implemented traits' methods was merged. The size of the HTML pages just became huge (more than 10 MB for some of them), making the web browsers take forever to render them or even crash. Luckily, it was fixed shortly after by #61505 which removed most of the newly added content.

As you may have guessed, we also added some minification on JS and on CSS files whereas we removed all uneeded characters like backlines and whitespaces in the HTML. We tried to go even further on the minification but we started to reach the limits.

To help you understand a bit more the problem here: rustdoc front-end provides a small search engine alongside the documentation in plain javascript. But it means that it also provides all the content to perform its search alongside it (in a file called search-index.js). Now, you might wonder why not providing this through a server instead of having it on the browser side? The reason is simple: because for local documentation, it would require to also start a search server, which is an absolute no-go.

However, this search index became a huge issue for embedded crates with a lot of items. Luckily, someone suggested to transform this JS index into a JSON one. I gave it a try with not much hopes, and it resulted in one of the biggest performance improvement in rustdoc front-end as you can see here.

I'll quote myself from the first comment in the PR:

I think it's explicit enough. :)

Web browsers API issues

At some point, some people opened issues about pages that took more than 10 seconds to render. Interestingly enough, I couldn't reproduce their performance issues, even on "less performant" computers. We finally found out that the issue was specific to Google Chrome (maybe even webkit), each explained why I didn't have it since I was only using Firefox. Once I switched web browsers, I was able to reproduce as well, so the bug hunt was finally able to really start.

The problem was that we are looping very often over nodes iterators that you can get from:

let iterator = document.getElementByClassName("some-class");
for (let i = 0; i < iterator.length; ++i) {}

This code looks fine right? And yet, because of that loop, the performance were terribly bad. The issue was not the loop in itself though, but the fact that we were making changes in the DOM, which forced Google Chrome to recompute the node iterators at every change. It's interesting to see that Firefox was almost completely unaffected by this though. Anyway, so how can we iterate over those nodes and update the DOM at the same time? The solution is actually pretty simple:

let iterator = document.getElementByClassName("some-class");
iterator = Array.prototype.slice.call(iterator);
for (let i = 0; i < iterator.length; ++i) {}

In here, we actually transformed the iterator into an array. With this, even if the DOM changes while we iterate over the nodes, it doesn't need to recompute the iterator since we now have what we could call an array of "node reference". Once this was merged, the page rendering speed on Google Chrome got back to normal. This was done in #56005. Since we're talking about this pull request, it also brought another big improvement:

Picking the right time to display

By default, all sections' sub-elements are hidden. As surprising as it might sound, if you put a breakpoint at the start of the javascript on a rustdoc page, you'll see very few content. The reason is simple: the browser performs DOM changes much faster when it's not displayed. So the idea is to make all the DOM changes and only then to display the content.

Normally, it's pretty fast, so unless you have a very old computer, you shouldn't be able to see the "loading content..." sentences under the sections.

Small implementation details

At this point, rustdoc front-end seems to be fast enough and we didn't receive new issues about performance in quite some time. Since we have an ongoing cleanup in the "back-end", I took this opportunity to clean things a bit in the front-end too. It led to a few small but interesting pull requests:

Conclusion

I still think, up to this day, that writing front-end code is one of the most complicated things to do in IT because of the numerous things to take into account. We had to provide some functions in case they didn't exist, we had to take into account some web browser API weirdness (don't get me started about Safari on iOS...).

Overall, I think that rustdoc provides now a front-end with good enough performance to not slow down people reading documentation. The next steps will very likely be on UX and UI. I think that rustdoc still doesn't provide best possible experience and I'd like it to get even better!

One last thing: if you ever encounter any performance issue, please open an issue on rust-lang repository so that we can fix it as soon as possible.

More to come soon!

Posted on the 22/01/2021 at 11:30 by @GuillaumeGomez

Next article

Interacting with data from FFI in Rust

Previous article

doc(alias) is stable and it's gonna be super useful!
Back to articles list
RSS feedRSS feed