10 ms·
How to speed up the Rust compiler in 2018: NLL edition
- simias 8y agoThe 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.
- Tuna-Fish 8y agoNLL is the default in the beta channel, and will ship as default on stable in exactly 30 days.
- nicoburns 8y agoYep, it should be in the next Rust release (in about a month). This will also be the first release to support Rust 2018.
- steveklabnik 8y agoTo 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.
- mitchty 8y ago> NLL will be enabled in Rust 2015 sometime early next year Wouldn't that be 2019 not 2015?
- portmanteaufu 8y ago"Rust 2015" and "Rust 2018" are referring to editions[1] of the language. > Rust ships releases on a six-week cycle. This means that users get a constant stream of new features. This is much faster than updates for other languages, but this also means that each update is smaller. After a while, all of those tiny changes add up. But, from release to release, it can be hard to look back and say "Wow, between Rust 1.10 and Rust 1.20, Rust has changed a lot!" > Every two or three years, we'll be producing a new edition of Rust. Each edition brings together the features that have landed into a clear package, with fully updated documentation and tooling. New editions ship through the usual release process. [1] https://rust-lang-nursery.github.io/edition-guide/editions/index.html https://rust-lang-nursery.github.io/edition-guide/editions/i...
- steveklabnik 8y agoNope. “Rust 2015” is a term of art in this context. https://blog.rust-lang.org/2018/07/27/what-is-rust-2018.html https://blog.rust-lang.org/2018/07/27/what-is-rust-2018.html
- mitchty 8y agoOk so 2015 gets features added to it or would this instead be the 2018 edition?
- steveklabnik 8y agoEverything goes in 2015 unless it would need to be backwards incompatible. Other editions, like 2018, have their feature sets frozen upon release. (Though there can be small details here; for example, at launch, 2018 will have the async keyword reserved but not implemented, the implementation will come later.)
- Dylan16807 8y agoSo 2015 and 2018 are going to have mutually incompatible features at some point in the future? If 2015 is the only one that won't be generally feature-frozen, it really should have had a different-looking name. Edit: That blog post says "We’ll continue to add features to Rust 2018 after its release, just like we continued to add features to Rust after the Rust 1.0 release." Is it being feature frozen or not? I'm confused.
- steveklabnik 8y agoWhen something lands in a particular edition, that’s a minimum edition. So a “Rust 2018 feature” will work in 2018 only, but a “Rust 2015 feature” will work in both 2015 and 2018. The “frozen” bit means that the features that are specific to Rust 2018 must be set when it is initially released. But new features that aren’t specific to it will still end up being added. So for example, Rust 1.31 will add the “impl_header_lifetime_elision” feature. It does not require Rust 2018, as it is backwards compatible. As such, it’s a “Rust 2015” feature, but it will work in both. A feature like async/await requires the async keyword. New keywords are not backwards compatible. As such, it will require Rust 2018, and will not work in 2015. However, if we release “Rust 2021” in three years, it will still work in that. Does that make sense?
- cornstalks 8y agoI'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?
- steveklabnik 8y ago“Rust 2015” does not mean “Rust 1.0.” Editions are a way to opt into changes that would be breaking. New features land in Rust 2015 unless they require breaking changes, and if they do, they must wait until the next edition to land, because otherwise they’d be breaking.
- kibwen 8y agoI 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.
- foldr 8y ago>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.
- gameswithgo 8y ago>Hmm. For me, fast compile times have vastly more ergonomic benefit than NLL. Well we weren't getting those anyway :(
- foldr 8y agoYeah, I'm sure that this specific performance regression will have a minimal impact on overall compile times. But slow compilation is often a case of the death of 1000 cuts. Small performance regressions are tolerated over a long period, and eventually add up.
- steveklabnik 8y agoCompiler performance is always at the front of everyone’s mind, and we’re doing a lot of work to improve it. It’s not an easy problem. Both smaller, “shave off 1%” and larger “re-think the algorithms and data structures” approaches are being taken, but that takes time and effort. We’ll get there. Both compile times and NLL are, and have been, at the top of requests from production and hobbyist users alike for a long time now.
- foldr 8y agoSure, but in the end, if you allow new features to regress compile times, and you keep adding new features, then compile times are probably going to increase in the long run. (The logic here being that there are always more people interested in implementing new features than there are people interested in improving compile times.) However, some of the other comments suggest that NLL is actually a bug fix. If so, then obviously a performance regression is better than a buggy borrow checker.
- leshow 8y agoI 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 8y agoThe 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.
- Ericson2314 8y agoGreat effort, but honestly I'm hoping that as incremental compilation gets better, stuff like this isn't worth it.
- steveklabnik 8y agoWhat does incremental compilation have to do with NLL?
- Ericson2314 8y agoI'm saying I hope this sort of microoptimization is not worth it. NLL itself definitely worth it, no disagreement there.
- killercup 8y agoThis also makes incremental compilation faster, doesn't it?
- Ericson2314 8y agoOf course, but if incremental compilation was in use and supremely fine grained, I'd hope nobody would care how fast compilation is because they'd be doing so much less of it.
- kurtisc 8y agoRust'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 8y agoCompile times have been dropping overall.
- piinbinary 8y agoBased 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?
- killercup 8y ago> Is there something genuinely computationally harder about compiling Rust? I'm not sure. Here's what I know: Rust has a complex type system. It also compiles whole packages (crates) at a time, and heavily uses monomorphization for generic code (that is probably less important for the type checking step but is quite heavy on the final compile-with-LLVM/link step). If it was necessary, I'm sure it'd be possible to trade in some memory usage for CPU time (IIRC, a lot of memory is used for caching/memoization). But aside from outliners like that "10GB less memory usage" fix, rustc doesn't use that much RAM.
- civility 8y agoI'm not a type theory guy, but it seems like you could rank Rust's type system somewhere in the realm of ML, Ocaml, Haskell, COQ, Agda, ATS, or something else. If Rust uses more memory and computation than those, then it's reasonable to assume there is another reason. I'm guessing you could have a very efficient implementation of Rust, but that it would be rewrite instead of an incremental fix. This statement is likely to be unpopular.
- simias 8y agoI 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.
- swiftcoder 8y agoI'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.
- kenhwang 8y agoMaybe 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.
- leppr 8y agoYes, named parameters or anonymous structs please. I'll take human-brain parsing speed over compile speed any day.
- Thiez 8y agoSince Rust doesn't have `null`, how do you imagine named parameters would work? Likewise, which traits would you expect an anonymous struct to implement?
- wtetzner 8y agoNamed parameters doesn't necessarily mean optional parameters. However, if you did want optional parameters, you could potentially have them be `Option` types.
- Thiez 8y agoBut without optional parameters, what is the point of named parameters? Hopefully most functions have a limited number of parameters, with these parameters being in a somewhat logical order, and the names of the values passed to the functions hinting at their purpose. In addition, more and more IDEs have the ability to display the parameter names. Some display them at all times, other on mouse-over. I write a lot of C#, and find I only used named parameters when a function has default parameters. Even then I prefer to avoid writing such functions myself, and Microsoft seems to agree [1]. [1]: https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1026-default-parameters-should-not-be-used?view=vs-2017 https://docs.microsoft.com/en-us/visualstudio/code-quality/c...
- superpermutat0r 8y agoI'm a bit skeptical that bitsets are more performant than arrays, indexing arrays should be superiorly faster.
- steveklabnik 8y agoWhy?
- superpermutat0r 8y agoIn 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.