If you build Servo a lot you may have noticed that compilation times have improved over the last few months. This is combination of multiple factors.

# Compiler optimization

Plain old optimization of the compiler’s code. Sometimes a rustup makes things a bit faster without us changing anything else. For example:

https://blog.mozilla.org/nnethercote/2016/10/14/how-to-speed-up-the-rust-compiler/
https://blog.mozilla.org/nnethercote/2016/11/23/how-to-speed-up-the-rust-compiler-some-more/


# Limited parallelism

Cargo can compile multiple crates at the same time, but a crate can only start after its dependencies are done. And in the default configuration, compiling one crate only uses one CPU core/thread. Servo’s dependency graph tends to be narrow near the end, in particular with the script crate that takes a lot of time to compile. This limits the overall available parallelism.


# Hardware

CPUs with many cores tend to make each core slower, to manage heat. Counter-intuitively this can make then worse for compiling Servo. If you’re spending (your employer’s) money on hardware, I recommend the highest single-thread performance you can find even if this means fewer cores. Currently, this is probably Intel’s i7-7700K. (4 cores, 8 threads, 4.2 GHz, easy to overclock to 4.6 GHz.)


# Codegen units

Compilation of a crate can be roughly split into two phases: analysis and code generation. The former includes parsing, type checking, borrow checking, etc. The latter is mostly LLVM.

rustc added a feature to get parallelism in code generation. With `rustc -C codegen-units=4` for example, the code to generate is split into four parts that are given separately to LLVM in threads that run in parallel.

This makes compilation faster but has a runtime cost: LLVM’s optimizer only sees one unit at a time, which reduces opportunities for inlining and other compile-time optimizations.

Also, only code generation is parallelized, the analysis phase still runs on a single thread. So Amdahl’s law limits how much speed improvement we can get with this.

In Servo we’ve enabled codegen-units=4 by default in debug mode, but not in release mode.

https://github.com/servo/servo/pull/14995


# LLVM assertions

When compiling LLVM (and so when compiling rustc) we can choose to enable LLVM assertions. There assertions can help find bugs (in LLVM or in rustc), but they have a runtime cost. (LLVM’s runtime, which is Servo’s compile time.) This cost can be significant, especially in release mode (where LLVM’s optimizer does a lot of work).

Official Rust builds have LLVM assertions enabled in Rust Nightly (to help catch bug), but not in the beta or stable release channels.

At our request, the Rust team started making alternative Rust Nightly builds without LLVM assertions, for some platforms. We’re now using them by default, except for some of our Continuous Integration builders (in order to preserve some of the coverage).

https://github.com/rust-lang/rust/pull/39754
https://github.com/servo/servo/pull/15559
https://github.com/servo/servo/pull/15564


# cargo check

Remember how I said a crate needs its dependencies to be compiled before it can start compiling? It actually only needs metadata (the results of the analysis phase), not the generated code.

Cargo added a `cargo check` command that skips code generation entirely (except for build scripts and build-dependencies). This saves a lot of time (I’ve measured up to 5× speed from `./mach clean`), though obviously you don’t get an executable at the end. Still, it can help for example during a refactoring: run `./mach cargo check` many times until compiler error are resolved, and only then run `./mach build` and/or `./mach test-unit`.

https://github.com/rust-lang/cargo/pull/3296
https://github.com/servo/servo/pull/14594

Follow-up work wanted: add a mach sub-command or option to do this with geckolib.

(It would also be nice for rustc to have a mode to write both metadata and full build, and signal somehow as soon as the former is ready; and have Cargo start building dependents on that signal while codegen for the dependencies is still running, increasing overall parallelism.)


# Preserving compilation output between CI runs

Previously, Servo’s Continuous Integration was configured to run `git clean` at the start of each build, with options such that anything not checked into the repository was removed. This included the `target` directory, so each pull request was recompiled from scratch.

We’ve changed the configuration to preserve files in .gitignore, which includes the target directory, so that some of the compilation output for previous builds can be reused. This makes pull requests faster to land after r+, and try runs faster to give results.

There is a risk that preserved files could be left by a previous builds in a broken state, leading to failures that only happen on CI. We’re monitoring this and will consider reverting this change it this turns out to be a problem.

https://github.com/servo/saltfs/pull/611


# Incremental compilation

Finally, the compiler feature everyone is waiting for.

In the current default configuration, entire crates are recompiled from scratch whenever the smallest part of their source or dependencies is touched. Incremental compilation changes this, caching many intermediate results so that the next build hopefully doesn’t need to do that work again.

Incremental compilation in Rust Nightly is currently in "beta". It mostly works (I’ve been using it exclusively for a couple months) but it’s buggy on some platforms and the speedups are not as good as one might hope. (Only code generation is cached, at the moment.)

It also uses a lot of disk space and memory. The `target` directory in one of my trees takes 26 GB. (I don’t know if there’s any cache eviction, so running `cargo clean` occasionally may be needed.) One of our CI builders with "only" 8 GB of RAM and no swap is unable to build the script crate with incremental compilation enabled.

So far we have *not* enabled incremental compilation by default in Servo. Still, you can try it by copying servobuild.example to .servobuild, then editing the latter to change `incremental = false` to `incremental = true` in the [build] section.

Please send us any feedback!

https://blog.rust-lang.org/2016/09/08/incremental.html
https://internals.rust-lang.org/t/incremental-compilation-beta/4721
https://github.com/servo/servo/pull/15390
https://github.com/servo/servo/pull/15565

--
Simon Sapin
_______________________________________________
dev-servo mailing list
dev-servo@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-servo

Reply via email to