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