Top
Best
New

Posted by Bogdanp 6 days ago

Why is the Rust compiler so slow?(sharnoff.io)
301 points | 426 commentspage 4
juped 6 days ago|
I don't think rustc is that slow. It's usually cargo/the dozens of crates that make it take a long time, even if you've set up a cache and rustc is doing nothing but hitting the cache.
charcircuit 6 days ago||
Why doesn't the Rust ecosystem optimize around compile time? It seems a lot of these frameworks and libraries encourage doing things which are slow to compile.
int_19h 6 days ago||
It would be more accurate to say that idiomatic Rust encourages doing things which are slow to compile: lots of small generic functions everywhere. And the most effective way to speed this up is to avoid monomorphization by using RTTI to provide a single generic compiled implementation that can be reused for different types, like what Swift does when generics across the module boundary. But this is less efficient at runtime because of all the runtime checks and computations that now need to be done to deal with objects of different sizes etc, many direct or even inlined calls now become virtual etc.

Here's a somewhat dated but still good overview of various approaches to generics in different languages including C++, Rust, Swift, and Zig and their tradeoffs: https://thume.ca/2019/07/14/a-tour-of-metaprogramming-models...

nicoburns 6 days ago|||
It's starting to, but a lot of people are using Rust because they need (or want) the best possible runtime performance, so that tends to be prioritised a lot of the time.
steveklabnik 6 days ago|||
The ecosystem is vast, and different people have different priorities. Simple as that.
kzrdude 5 days ago||
Lots of developers in the ecosystem avoid proc macros for example. But going as far as avoiding monomorphisation and generics is not that widespread
WalterBright 5 days ago||
I suspect that it is because the borrow checker needs to do data flow analysis. DFA is normally done in the optimizer, and we all know that optimized builds are quite a bit slower, and that is due to DFA.

DFA in the front end means slow.

steveklabnik 5 days ago|
As said many times in this thread already, the borrow checker is virtually never a significant amount of time spent while compiling.

Also, the article goes into great depth as to what is happening there, and the borrow checker never comes up.

WalterBright 5 days ago||
That makes me curious as to how Rust does the DFA. What I do is construct the data flow equations as bit vectors, which are then solved iteratively until a solution is converged on.

Doing this on the whole program eats a lot of memory.

steveklabnik 5 days ago||
I'm not an expert on this part of the compiler, but what I can tell you is that Rust uses multiple forms of IR, and the IR that the borrow checker operates on (https://rustc-dev-guide.rust-lang.org/mir/index.html) already encodes control flow. So doing that construction, at least, is just a normal part of the compiler, and isn't part of the borrow checker itself.

However, in my understanding, it takes that IR, does build a borrow graph, computes liveliness, and then does inference. There's been a number of details in how this has changed over the years, but it's vaguely datalog shaped.

WalterBright 5 days ago||
Because there are cycles in the flow graph, I do not see how liveness can be computed in general without iteration and convergence on a solution.
senderista 6 days ago||
WRT compilation efficiency, the C/C++ model of compiling separate translation units in parallel seems like an advance over the Rust model (but obviously forecloses opportunities for whole-program optimization).
woodruffw 6 days ago||
Rust can and does compile separate translation units in parallel; it's just that the translation unit is (roughly) a crate instead of a single C or C++ source file.
EnPissant 6 days ago||
And even for crates, Rust has incremental compilation.
Devasta 6 days ago||
Slow compile times are a feature, get to make a cuppa.
zozbot234 6 days ago|
> Slow compile times are a feature

xkcd is always relevant: https://xkcd.com/303/

randomNumber7 6 days ago||
On the other hand you get mentally insane if you try to work in a way that you do s.th. usefull during the 5-10 min compile times you often have with C++ projects.

When I had to deal with this I would just open the newspaper and read an article in front of my boss.

PhilipRoman 5 days ago||
Slow compile times really mess with your brain. When I wanted to test two different solutions, I would keep multiple separate clones (each one takes about 80GB, mind you) and do the manual equivalent of branch prediction by compiling both, just in case I needed the other one as well.
jeden 5 days ago||
why rust compiler create so BIG executable!
RS-232 6 days ago||
Is there an equivalent of ninja for rust yet?
steveklabnik 6 days ago|
It depends on what you mean by 'equivalent of ninja.'

Cargo is the standard build system for Rust projects, though some users use other ones. (And some build those on top of Cargo too.)

fschuett 5 days ago||
For deploying Rust servers, I use Spin WASM functions[1], so no Docker / Kubernetes is necessary. Not affiliated with them, just saying. I just build the final WASM binary and then the rest is managed by the runtime.

Sadly, the compile time is just as bad, but I think in this case the allocator is the biggest culprit, since disabling optimization will degrade run-time performance. The Rust team should maybe look into shipping their own bundled allocator, "native" allocators are highly unpredictable.

[^1]: https://www.fermyon.com

jmyeet 6 days ago||
Early design decisions favored run-time over compile-time [1]:

> * Borrowing — Rust’s defining feature. Its sophisticated pointer analysis spends compile-time to make run-time safe.

> * Monomorphization — Rust translates each generic instantiation into its own machine code, creating code bloat and increasing compile time.

> * Stack unwinding — stack unwinding after unrecoverable exceptions traverses the callstack backwards and runs cleanup code. It requires lots of compile-time book-keeping and code generation.

> * Build scripts — build scripts allow arbitrary code to be run at compile-time, and pull in their own dependencies that need to be compiled. Their unknown side-effects and unknown inputs and outputs limit assumptions tools can make about them, which e.g. limits caching opportunities.

> * Macros — macros require multiple passes to expand, expand to often surprising amounts of hidden code, and impose limitations on partial parsing. Procedural macros have negative impacts similar to build scripts.

> * LLVM backend — LLVM produces good machine code, but runs relatively slowly. Relying too much on the LLVM optimizer — Rust is well-known for generating a large quantity of LLVM IR and letting LLVM optimize it away. This is exacerbated by duplication from monomorphization.

> * Split compiler/package manager — although it is normal for languages to have a package manager separate from the compiler, in Rust at least this results in both cargo and rustc having imperfect and redundant information about the overall compilation pipeline. As more parts of the pipeline are short-circuited for efficiency, more metadata needs to be transferred between instances of the compiler, mostly through the filesystem, which has overhead.

> * Per-compilation-unit code-generation — rustc generates machine code each time it compiles a crate, but it doesn’t need to — with most Rust projects being statically linked, the machine code isn’t needed until the final link step. There may be efficiencies to be achieved by completely separating analysis and code generation.

> * Single-threaded compiler — ideally, all CPUs are occupied for the entire compilation. This is not close to true with Rust today. And with the original compiler being single-threaded, the language is not as friendly to parallel compilation as it might be. There are efforts going into parallelizing the compiler, but it may never use all your cores.

> * Trait coherence — Rust’s traits have a property called “coherence”, which makes it impossible to define implementations that conflict with each other. Trait coherence imposes restrictions on where code is allowed to live. As such, it is difficult to decompose Rust abstractions into, small, easily-parallelizable compilation units.

> * Tests next to code — Rust encourages tests to reside in the same codebase as the code they are testing. With Rust’s compilation model, this requires compiling and linking that code twice, which is expensive, particularly for large crates.

[1]: https://www.pingcap.com/blog/rust-compilation-model-calamity...

b0a04gl 6 days ago|
rust prioritises build-time correctness: no runtime linker or no dynamic deps. all checks (types, traits, ownership) happen before execution. this makes builds sensitive to upstream changes. docker uses content-hash layers, so small context edits invalidate caches. without careful layer ordering, rust gets fully recompiled on every change.
More comments...