I appreciate that they continue to speed up compilation/add flags. If the compiler gets slower every release it will eventually become unusable. I think the long compilation times will continue to get worse in the future as the rust compiler gets better at checking for errors, and I’m not sure what can really be done about that.
One thing that irks me is that they throw around the phrase zero cost abstraction a lot. And I just don’t buy it. Sure I get it, lots of things can be monomorphized so we don’t pay a runtime cost, but there is still a large compilation hit to do that, and it’s not zero cost to the programmer. One need only to look at Chandler Carruth’s cppcon talk “there are no zero cost abstractions” to understand that.
The checks aren't the longest part of compile times, so that's not really it.
It is true that there are aspects of the design that lead to slower compile times than other languages, but it's more of what you're talking about at the end: monomorphization is great for a lack of runtime speed, but increases compile times. There's a lot more complexity here than simply "rust's safety checks are slow."
(And, "zero cost abstractions" was always speaking about runtime cost.)
Incidentally your first question is one that's very amenable to testing. When does the Rust compiler spend most of it's time? Is it at the checking stage?
rustc has a self-profiler that can be used to answer this question [0], as well as a mode that times compiler passes [1].
There's no single reason the Rust compiler is slow, as it depends quite heavily on the code being compiled. For some codebases, LLVM code takes up most of the time; in other codebases (e.g., extremely generic-heavy codebases), it'll be checking-related passes.
[0]: https://github.com/rust-lang/measureme/blob/master/summarize...
Yes, you're technically correct, but also being unreasonable.
It was pretty clear that "zero cost abstractions" refers to the runtime aspect. That is the default meaning, there's nothing to "get back to".
Making a compiler fast is largely a goal you have to continuously strive for, and for the most part doesn't come for free, no matter the language. Saying "it's an issue with the compiler" makes it sound like the Rust team consciously made some horrible design decisions or something, but that isn't evidently clear from anything we can immediately observe.
The story I suspect is correct and vastly, vastly more boring is they probably focused on "the language" (features, APIs, etc) for a really long time and only started focusing on compiler performance in more recent memory, once users started getting more irritated, so it ran away from them a lot. That seems to happen to every compiler these days; features are what get monetary and mindshare support from users. Down-in-the-dirt .5% performance wins day in and day out, which you have to do a lot of to actually improve things most times, normally aren't fun, nor something people trot out the red carpet for.
> I think the long compilation times will continue to get worse
No, it's more likely they'll get better - that has been the trend, and as I mentioned there are projects in the works that can likely cut the compile times in half.
You get the same "zero cost abstraction" effect in lower-level languages like C (but you need to feed it "dumb code" to see a similar effect). Nobody talks about "zero cost abstractions" in the context of C programming because C doesn't encourage to "obfuscate" source code with high level abstractions.
Not sure if optimizer passes are the main reason for Rust being slow though, I'd guess it's more the static code analysis, e.g. a C/C++ compiler with static code analysis enabled is also many times slower than regular compilation (but Rust should have an advantage there, because Rust doesn't allow as much freedom as "less correct" languages, so the static analysis can be more focused).
Actually rustc has many modern features that say C++ compilers don't. For example, clang is not an incremental compiler, nor does it do parallel codegen. Rust also has had work on multithreading support. These features were introduced all after rustc already existed. It would have been unimaginably hard to retrofit clang to this, as C++ is way harder to refactor than Rust.
So I'd put it onto the design instead of the compiler. But it's not the safety features of Rust that cause the main slowdown, at least not directly. Yes, borrow checking is some additional step rustc has to do, and NLL introduction took a major engineering project to not regress compiler performance, but overall these safety checks don't make up such a large part of the compile process.
First, Rust has larger compile units than C. If you change one file in Rust, the entire crate has to be recompiled, while in C only the single file needs recompilation (unless it's a header, then everything that imports it needs recompilation). That's also why incremental compilation is much more important for Rust than it is for C (and it increasingly becomes important for modern C++ as compile units increase).
Second, in Rust you compile everything, including your dependencies. It's not like in C or C++ where you install *-dev packages for the heavy libraries. In fact, if you compiled everything yourself in the C/C++ world, often you'd get similar compile times or even worse ones. Rust has an unstable library format. It's literally just mmap'ed internal data structures. Two different versions of the compiler can't reuse the same library, after a compiler update you have to recompile everything from scratch, and if you want to be able to add new dependencies or cargo update, you need to always use the latest compiler, which gets released quite often. So many projects that I maybe touch once every 6 months I basically have to recompile from scratch again. This is all due to design and policy questions, not because the compiler itself is slow.
Third, in Rust you statically link everything. This puts greater load onto the linker which now has to copy large amounts of data to obtain large binaries. There is no stable ABI so you can't create a dynamically linked library with a safe interface (you can of course create one with an unsafe C interface but that's not nice). There is also no cargo support for it.
Fourth, heavy use of monomorphization. A lot of libraries in Rust are generic either on lifetimes or on types. Lifetimes can be stripped, but if your code is dependent on a type it gets copied and then recompiled for every different type. This is a design question and has benefits in the final program as the code can be optimized for the type. But it has to be optimized. This incurs a compile time cost.
C++ is not one trick pony, it is an ISO standard with multiple implementations.
A debug backend or interpreted based one, e.g. ocaml, GHC hugi, could help quite substantially.
That is not really true. Yes, it would help first compiles a bit, but only the initial compiles. That's a one-time improvement, that while important, doesn't solve the big problems.
Anybody know of a 2000 vs 2020 C++ compilation comparison?
It's possible to get the linker to keep the debug info by tweaking the section attributes. By default all debug info related sections have the `S_ATTR_DEBUG` flag. If that is replaced with the `S_REGULAR` flag the linker will keep those sections. The DMD D compiler does this for the `__debug_line` section [1][2][3]. This allows for uncaught exceptions to print a stack trace with filenames and line numbers. Of course, DMD uses a custom backend so this change was easy. Rust which relies on LLVM would probably need a fork.
[1] https://github.com/dlang/dmd/pull/8168 [2] https://github.com/dlang/dmd/blob/33406c205b76a8c2b5fb918da1... [3] https://github.com/dlang/dmd/blob/33406c205b76a8c2b5fb918da1...
I'm not sure that's true. I was not getting line numbers in backtraces in a Rust program I developed because I copied the executable to another directory. I had to add a symlink to the dSYM directory to make it work.
If you do want to retain it entirely you can set `split-debuginfo` to `off` and it will remain in the final executable.
The advantage of the `unpacked` version is that the debug info just stays associated with the original object files instead of the executable. This makes handling them harder obviously but is good enough for a lot of use cases.
I wish my dev builds only took 14 seconds...
This project is a very small hyper/tokio backend API. However, I also work on a CLI tool [1] I created which has very fast recompile times, usually less than a second. Mostly because it's pretty light on dependencies.
https://github.com/est31/cargo-udeps/commit/e550d93c7a6d756e...
apt-get install rustlibXYZ-dev can't come soon enough.
cargo install --no-default-features
To avoid building support for all storage backends. You can then add back any backend you're actually using.However, the point is actually how it is a big hurdle to overcome having to compile crates with npm like dependency trees all from scratch, vs other compiled languages integrated into the distribution package repositories.
https://github.com/rust-lang/rust/pull/79570
Maybe the default will be switched in the future.
https://github.com/rust-lang/cargo/pull/9298
https://doc.rust-lang.org/nightly/cargo/reference/profiles.h...
Every release doesn't make every workload faster, but over a long time horizon, the effect is clear. Rust 1.34 was released in April 2019 and since then many crates have become 33-50% faster to compile, depending on the hardware and the compiler mode (clean/incremental, check/debug/release).
Interestingly, the speedup mentioned in OP won't show up in these charts because that's a change on macOS and these benchmarks were recorded on Linux.
What is expected to be a gamechanger is the release of cranelift in 2021 or 2022. It's an alternate debug backend that promises much faster debug builds.