
How to speed up the Rust compiler in 2018: NLL edition - markdog12
https://blog.mozilla.org/nnethercote/2018/11/06/how-to-speed-up-the-rust-compiler-in-2018-nll-edition/
======
simias
The big news for me here is that NLL seems about to ship, that's great to
hear. That will alleviate easily 90% of my frustrations with the current
block-based checker.

~~~
steveklabnik
To be a bit more precise than my siblings:

NLL is on by default in the Rust 2018 mode of the Rust 1.31 release, shipping
December 6.

NLL will be enabled in Rust 2015 sometime early next year, we do not have a
precise release yet.

~~~
cornstalks
I'm a bit confused by this. I thought (probably incorrectly) that the point of
Rust editions was that if you targeted Rust 2015, it would compile with Rust
v1.0.

But if you enable NLL in Rust 2015 mode, that code will no longer compile with
Rust v1.0. Which means we're still back at square 1 (meaning each crate has to
specify which compiler version it requires).

Could you help enlighten me on why this is safe to backport to Rust 2015?

~~~
kibwen
I think you're talking about forward compatibility, which is distinct from
backward compatibility.

With backward compatibility, old programs will work with new versions of the
compiler (alternatively, for web browsers, "old websites will work with new
versions of the browser" (or for operating systems, "old binaries will work
with new versions of the OS" (or for game consoles, "old games will work with
new versions of the console", etc.))).

In this case, code written as of Rust 1.0 should continue compiling with Rust
2015, respective of the compatibility guarantee ( [https://blog.rust-
lang.org/2014/10/30/Stability.html](https://blog.rust-
lang.org/2014/10/30/Stability.html) ). By this definition, Rust is backwards-
compatible.

What Rust is not, and has never been, is forwards-compatible. Very few systems
in the wild are forwards-compatible. A C++ compiler from 2003 is not able to
compile C++17 programs, and is thus not forwards-compatible. Likewise, web
browsers from 2003 cannot render web pages from 2018, Windows ME cannot run
binaries compiled for Windows 10, PS2 cannot play PS4 games, and so on.

 _> meaning each crate has to specify which compiler version it requires_

Every Rust crate that does not specify a compiler version is compiled in 2015
mode. It must be this way, for backwards-compatibility. However, when you run
`cargo new`, cargo should automatically insert the directive specifying the
most recent edition supported by your compiler. In this way, Rust can
gradually push the ecosystem forward without any additional burden to the user
and without leaving behind any code that doesn't update.

------
swiftcoder
I'd really like to see those BitSet/BitMatrix types factored out into their
own crate. Ever since BitSet was ejected from std, we don't have very many
options for well-maintained and performance optimised BitSet types.

------
foldr
>I’m not entirely happy even with this level of performance regression, but
for now I have run out of ideas for improving it further. The new borrow
checker is a lot more sophisticated and tracks a lot more data, so strict
performance parity is a tough ask. Nonetheless, given how bad performance was
a few months ago, I’m happy that we’ve got it down to a level where most
people probably won’t notice any difference. Given that the new borrow checker
makes Rust a significantly nicer and easier language to use, I hope it’s an
acceptable trade-off.

Hmm. For me, fast compile times have vastly more ergonomic benefit than NLL.

~~~
almostdeadguy
As mentioned here [1], NLL doesn't just provide ergonomic benefits, it
actually fixed soundness bugs w/ the previous borrow checker.

[1]:
[http://smallcultfollowing.com/babysteps/blog/2018/10/31/mir-...](http://smallcultfollowing.com/babysteps/blog/2018/10/31/mir-
based-borrowck-is-almost-here/#diagnostics-migration-and-performance)

~~~
foldr
Is that because the lexical model was fundamentally unsound? Or just because
various bugs were turned up in the course of implementing NLL?

~~~
kibwen
It's because the previous implementation didn't track enough information to
properly cover some edge cases of the theoretical model. Extending the old
implementation to track such information would have been congruent to
implementing the new borrow checker, with all the additional overhead that
implies (or even more, considering that the new implementation has been
designed with this in mind from the ground up).

~~~
foldr
So this is actually a major bug fix rather than an ergonomic improvement?

~~~
steveklabnik
It’s both.

~~~
foldr
Ok, good to know. That's definitely not how it's presented in the blog post.

~~~
steveklabnik
Most people care way more about the ergonomic improvement than they do the
soundness fixes, because the soundness issues are very, very rare, but the
ergonomic pain is very present.

~~~
foldr
No-one would suggest that the dev team ought to hold off on a _soundness fix_
for performance reasons. It seems like it would be worth mentioning that
aspect of NLL when raising the issue of compile times. I certainly would not
have made my original comment if the blog post had made that clear.

------
piinbinary
Based on how much ram that was using, it sounds like Rust is a language that
couldn't have been practical until the past ~15 years or so. That is
interesting to me, given how long languages like ML have been around. Is there
something genuinely computationally harder about compiling Rust?

~~~
simias
I don't know a lot about compilers but as far as I can tell from a compilation
complexity standpoint Rust is C++ on steroids. Like C++ (and unlike higher
level ML languages) Rust does most of the type system heavy lifting statically
at compile-time. That results in potentially faster and simpler machinecode
with fewer runtime dependencies but it also means that the compilation "tree"
can become extremely complex since you need to resolve all types at compile-
time and emit code for it. You can't just emit dynamic code that will dispatch
at runtime.

Heavily templated C++ code is notoriously slow to compile so it's no surprise
that heavily genericized Rust code doesn't fare much better.

On top of that Rust builds one crate at a time basically, it's not like C
where you typically have plenty of smaller compilation units that are black-
boxes to one-another. C++ has the same compilation model but as mentioned
above templates need to be resolved completely at compile-time so all
templated code ends up in headers and therefore in copy/pasted by the
processor every compilation unit it's used in.

And then after all that in Rust you have the borrow checker which probably ads
a non-negligible runtime overhead.

I agree that it's an interesting evolution as far as programming languages are
concerned. I consider that Rust is overall superior to C but at the same time
in the seventies nobody could have run a Rust compiler on anything more than a
Hello World. Meanwhile I've just compiled (with optims) an auto-generated C
file of about 20k lines almost instantly on my 2018 computer.

~~~
pjmlp
You can get reasonably fast compile times in C++ with a mix of incremental
compilation, incremental linking, binary libraries and if using clang or VC++,
experimental modules support.

Still slower than compiling Ada, Delphi, Eiffel, D code, though.

------
leshow
I was writing some code in 2018 recently and didn't realise how much NLL
really makes a difference. I went to push the a change to a CI and realised I
would have to heavily refactor in order to get things working on stable.

Your work is very much appreciated, thank you.

~~~
wild_preference
The first place I needed it was the simple case of borrowing inside a `match`
case and short-circuiting `return None` in one of the branches.

Thankfully someone in IRC told me back then to just switch on the feature
flag. Never looked back.

------
kenhwang
Maybe it's just me, but faster compile times really isn't anywhere near the
top of ask list. Mentally I just bucket compile times in seconds, minutes,
hours. Almost nothing compiles in seconds, which I consider the minimum to be
non-disruptive. Minutes is fine and pretty much the norm (especially in Scala
land), I take that time to go to the bathroom, refill my coffee, browse around
on HN; it's honestly not a big deal. When compile times start creeping into
the hour mark, yeah, then I'm annoyed because it becomes a "once a day" build
task.

Rust is still solidly in the "minutes" bucket for compile time. I don't see it
ever breaking into the "seconds" bucket. That's fine. I'll take ergonomics
improvements every time.

~~~
simias
>Almost nothing compiles in seconds

You just made me feel glad I'm mainly a C dev these days. Since you typically
rebuild one C file at a time (unless you make changes to header files
potentially) compilation times of a fraction of a second are the norm here.

That being said I agree with you that between 1mn compile time and 1mn10s
compile time it might not make all that much of a difference in practice.

However one thing Rust does that mitigates these problems a little is that
diagnostics and errors are typically generated early in the build process, so
generally if you see that it started off well you can focus your attention
elsewhere (or even interrupt the build early to start a new one if you make
small, incremental changes).

In this context I assume that the NLL checker added time to this early phase
of the boot process and it might be worth optimizing that bit in particular.
If you have a lifetime issue you don't want to wait 30s to have your build
error.

~~~
paavohtl
> However one thing Rust does that mitigates these problems a little is that
> diagnostics and errors are typically generated early in the build process,
> so generally if you see that it started off well you can focus your
> attention elsewhere (or even interrupt the build early to start a new one if
> you make small, incremental changes).

You can also run the compiler with `cargo check` and it only does type- and
borrow checking. I haven't worked on any huge Rust codebases, but in my
experience it usually takes only a fraction of a second at best or a few
seconds at worst.

~~~
jblindsay
I have a large (~140k SLOC) geospatial analysis project written entirely in
Rust, and I can confirm that it takes me about 3.5 min to cargo build
--release on my Macbook pro and only about 12 s to cargo check. It is a life
saver for productivity although I would still welcome a reduction in those
compile times. The data sets that I use to test new tools are often so large
that cargo build is not an option for testing. I have to use release mode. I
have really enjoyed programming in Rust since starting the project a year and
a half ago. If there is one thing I could change about the language it would
be the compile times.

------
superpermutat0r
I'm a bit skeptical that bitsets are more performant than arrays, indexing
arrays should be superiorly faster.

~~~
steveklabnik
Why?

~~~
superpermutat0r
In my experience, almost every case of trying to replace arrays with bitsets
resulted in worse performance.

Indexing arrays is definitely faster. Bitsets also do array indexing but have
to target particular bits too.

If I want to index 1309402 bit, I have to calculate where it is in memory, for
an array representing a bit, it's super quick.

Rarely have I encountered that memory occupied by an array is a bottleneck.

------
kurtisc
Rust's infamously long compilation times being increased here for the sake of
features highlights what I like the most about Rust: if we're willing to use
the increasing power of customers' machines for languages with GCs, etc., with
the aim of reducing development time, surely we can spare some power on the
developers' machines for some static analysis.

~~~
steveklabnik
Compile times have been dropping overall.

------
Ericson2314
Great effort, but honestly I'm hoping that as incremental compilation gets
better, stuff like this isn't worth it.

~~~
steveklabnik
What does incremental compilation have to do with NLL?

~~~
Ericson2314
I'm saying I hope this sort of microoptimization is not worth it.

NLL itself definitely worth it, no disagreement there.

