Skip to content

Instantly share code, notes, and snippets.

@notriddle
Last active March 12, 2021 19:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save notriddle/0f04510906db3080a52d47bf923691f0 to your computer and use it in GitHub Desktop.
Save notriddle/0f04510906db3080a52d47bf923691f0 to your computer and use it in GitHub Desktop.
How to speed up rustdoc in 2021

By "rustdoc", I mean the API documentation webapp used by Rust. The Book is rendered using a different application, called mdBook, and only some of this advice is really applicable to it.

I hope you take my copycat txties as the compliment that I intend, by the way.

Step 1: get the rust toolchain

The first step is to get the Rust code. To help compare the old version with the new one, I set up a separate worktree for the new version, and keep the old version around. It's a lot like cloning the repository twice, but it doesn't require two downloads.

user=notriddle
cd ~/Development
git clone https://github.com/$user/rust rust0
cd rust0
git remote set-url origin git@github.com:$user/rust
git remote add upstream https://github.com/rust-lang/rust
git worktree add ../rust1

Step 2: Build rustdoc and the standard library docs

Within the two repositories, I set up a config.toml file. Since I will not be changing codegen or anything, I don't need to build LLVM myself.

cp config.toml.example config.toml
sed -i -e 's:#incremental = false:incremental = true:' -e 's:#download-ci-llvm = false:download-ci-llvm = true:' config.toml

And now, build it.

./x.py doc --stage 1 --open

Since rustdoc is an extension for the compiler, building it takes awhile, though the two options I flipped in config.toml should help a lot.

The file that pops up links to the various generated documentation files. Some of them are generated by mdBook, but this blog post is about the "extensive API documentation."

Step 3: Basic CPU profiling

There are two aspects of rustdoc that we might want to profile: download time and CPU time, and both are linked to payload size (how many bytes in a file).

If you want to profile CPU in Firefox, open F12 developer tools, pick the Performance tab, click the performance recording button (it looks like a stopwatch, and has the tooltip "Toggle the recording stage of a performance recording"), then do whatever it is you want to take a profile trace of.

search-profile

The search profile shows a timeline with line green line chart ("FPS", or "Frames Per Second"), and two rows saying when events like DOM Event and Recalculate style happen.

The thing you probably want to worry about is stopping the FPS from dipping down, like it did between 2880 and 3200 ms. I can drag select a part of the timeline, and use the section below will show just the part I selected.

search-profile-zoom

Apparently, most of the time was spent in a "DOM Event", probably JavaScript code in an event handler? I can click the little arrow to see what's up in there...

search-profile-details

There's an event handler in startSearch, and it's doing a bunch of work in there. Let's open it up and see what's in that code...

search-profile-minified

... and it's minified.

After searching for rustdoc in the bootstrap source directory, and adding the appropriate lines of code to tunnel the --disable-minification arg through it..., it looks much better.

        var tds = search.getElementsByTagName("td");
        var td_width = 0;
        if (tds.length > 0) {
            td_width = tds[0].offsetWidth;
        }
        var width = search.offsetWidth - 40 - td_width;
        onEachLazy(search.getElementsByClassName("desc"), function(e) {
            e.style.width = width + "px";
        });

Well, they've already done some stuff to make sure the browser doesn't totally lock up, but they're still doing a bunch of layout work in JavaScript instead of using CSS. That's why the profile shows so much purple; every time that thing does e.style.width, it takes another trip through the CSS and layout engines to fix up the appearance of that column.

Does Blame show anything useful here? No. Blame says that the line I'm searching for doesn't even exist!

And that's when I realized that I was running on the master branch of my own repo, which was massively outdated compared to rust-lang/rust's master. They already fixed this particular issue!

At least the docs-minification config.toml option work is still useful, since it hasn't been added by anyone else between November 2020 and March 2021.

Anyway, here's some performance tweaks that weren't invalidated by me accidentally running on an ancient version of the code

  • rust-lang/rust#83059 — Add an option to disable minification in rust-lang/rust's config.toml. This is not, strictly, a performance improvement, but it should make work on the rustdoc frontend easier.

  • rust-lang/rust#83003 — When you're just trying to reduce the size of a payload, the only profiler you need is wc -c. The funny fact was that I started out thinking about V8's array type optimizations, and wondering if I'd be able to notice them in the rustdoc frontend's search routine, before realizing that clustering together data of the same type would probably make the search index massively more compressible even if it didn't affect the CPU profile at all.

  • rust-lang/rust#82809 — Switches to using substrings instead of array ops for manipulating paths. You probably don't want to use .split("::") in JS just for one item, since it'll still scan the entire array instead of just finding the last one.

  • rust-lang/rust#82807 — Get rid of a conditional that can never evaluate to false.

  • rust-lang/rust#82787 — Remove a totally dead function.

  • rust-lang/rust#80569 — Uses Array.prototype.filter instead of using a hand-coded filter built around splice(). This wasn't actually my idea, but the original pull request had been split up, and this one almost fell through the cracks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment