Why doesn't Rust care more about compiler performance?
Perhaps the most often repeated complaint about Rust is its slow feedback loop and long compilation times. I hear about it all the time; in Rust podcasts, blog posts, surveys, conference talks or offline discussions. I also regularly complain about it, being a Rust user myself!
Recently, in addition to the usual compile times complaints, I also started noticing the following sentiments being expressed by frustrated Rust developers: “Why doesn’t the Rust Project care more about this pressing and known issue? Why don’t they do something about it?”. As a member of the Rust compiler performance working group, I take these questions very seriously, and I of course have some opinions on the topic. In this blog post, I would like to offer some thoughts that might serve as answers to these (and other similar) questions.
Disclaimer: all opinions expressed in this post are mine only and do not necessarily represent the broader views of the Rust Project (the diverse set of contributors and maintainers that contribute to the Rust toolchain).
Do we even care?
First, let me assure you - yes, we (as in, the Rust Project) absolutely do care about the performance of our beloved compiler, and we put in a lot of effort to improve it. We triage performance improvements and regressions each week. We run a comprehensive benchmark suite after every merged PR. We enthusiatically welcome any performance improvements (unless they have complicated trade-offs, more on that later), and try to quickly revert (or fix) PRs that introduce performance regressions. Very smart people are continously working on finding bottlenecks and speeding up the compiler. And some significant improvements to make compilation faster by default are currently in the works.
And all this effort shows results; Rust build performance has improved quite a lot over the past few years! When we talk about the long-term trends, we usually show this dashboard, but I find it a bit dull, because it averages results of several benchmarks together and the benchmarks are quite short, so they start running into diminishing returns. Instead, I did a little experiment to show how did the build performance evolve on my favourite test subject, hyperqueue. I took its first commit (from March 20211) and compiled it with a few different Rust compiler versions, with ~1 year increments between them2. I did this on a relatively modest laptop with 8 AMD cores running Linux. Here are the results:
rustc version |
Clean build [s] | Incremental rebuild [s] | Speedup (clean build) |
---|---|---|---|
1.61.0 (May 2022) |
26.1s | ~0.39 | - |
1.70.0 (June 2023) |
20.2s | ~0.37 | 1.29x |
1.78.0 (May 2024) |
17.0s | ~0.30 | 1.53x |
1.87.0 (May 2025) |
14.7s | ~0.26 | 1.77x |
On this benchmark, the compiler is almost twice as fast than it was three years ago. Of course, this is just a single data point, in different cases the speedup will be larger or smaller, and it will likely be smaller in general on other platforms than x64 Linux, because we spend the most effort optimizing that specific target. But the fact remains that the performance of the compiler is continuously improving.
It’s still not fast enough though
Now, all that is nice, but it doesn’t change the fact that for many Rust developers, compilation performance is still a big bottleneck, and they could be much more productive if the feedback loop was an order of magnitude (or more) faster. To be fair, this will depend on who you ask, e.g. some C++ developers don’t mind Rust’s compilation times at all, as they are used to the same (or worse) build times, while Python programmers likely aren’t very impressed by Rust’s feedback loop speed. But it’s definitely not a stretch to say that for many users, Rust compilation just isn’t fast enough today, and that is a problem.
Before we examine that problem in more detail, I think that it’s interesting to think about whether the problem is even fundamentally solvable, and what is the end goal that we would eventually like to achieve. Can Rust ever hope to achieve near-instant (re)build speeds, with its complex type system, borrow checking, monomorphization, proc macros and build scripts, large translation units, machine code generation and a “rebuild the world from source” compilation model? When it has a historical track record of favoring runtime (rather than compilation) performance at nearly every opportunity?
I think that it depends on the use-case. If we’re talking about building projects with hundreds of dependencies from scratch or doing super-duper LTO-optimized release builds, I think that we won’t ever get to a point where the build will be truly instant. On the other hand, I personally believe (although others might disagree!) that it is not fundamentally impossible to have a Rust compiler that will be able to almost instantly rebuild a Rust project of nearly any size in incremental (and at least moderately optimized) mode after making a small change to the source code, one where rebuilds are truly O(number of performed changes)
and not O(size of the codebase)
. We might have to make some trade-offs, particularly around the resulting runtime performance, but I think that we can get there. In fact, we can even see examples that can already achieve something akin to this (in somewhat limited scenarios for now)
today!
And it’s not like we don’t have some ideas on how to get (at least closer) to this goal. There are several “northstar” approaches for speeding up the compilation process in various ways, like the parallel frontend, alternative codegen backends, defaulting to a faster linker, deferring code generation (MIR-only rlibs, -Zhint-mostly-unused), avoiding useless workspace rebuilds, having smarter incremental compilation (including incremental linking or even binary hot-patching), and many others.
Some of these approaches are not prepared for wider usage yet, but others are, and you can opt into them today. They usually (but not always) help. I believe that if we had a magic wand and we could land all these changes today, we could get very snappy incremental rebuilds for a large (certainly much larger than today) fraction of Rust projects.
And it’s not like “only” the broader Rust community would benefit from this. Improving build performance makes Rust toolchain contributors themselves much more productive. It reduces the time required to build the compiler itself when making changes to it, it speeds up our CI workflows, reduces the time that we have to wait for tests and compiler performance benchmarks… Everyone would benefit! So what’s blocking us from making progress faster? WHy dON’t We dO SOmEtHIng ABoUt iT??!3
So why don’t we do more?
This is a tough question, one that doesn’t have a simple answer. I will try to provide a variety of reasons (in mostly arbitrary order) why I think we have not been making progress faster, based on my personal experiences working within the Rust Project.
Technical reasons
First, let’s start with the technical reasons, which are in a way the most straightforward. Simply put, making non-trivial improvements to the performance of the Rust compiler is hard! It is a large codebase4 that has a lot of technical debt risk, as probably all large (compiler) codebases do, and although we provide a guide for contributing to it, it still takes time before you can understand what’s going on. In fact, there is probably not a single person that understands the whole compiler codebase (maybe except for compiler-errors ).
While it is possible to make performance improvements even without deeply understanding how the compiler works, by profiling and micro-optimizing its various parts, this approach can only get you so far, and most of the low-hanging fruit has already been picked5. A lot of things have been optimized and overfitted to reach a “local performance minimum” on a variety of benchmarks and use-cases, so even if an improvement is made for some use-cases, it often regresses something else, which can lead to that localized improvement not being accepted.
We also have to consider various trade-offs when deciding whether to land certain performance optimizations. For example, we know a few tricks that could make the compiler a bit faster on x64 Linux, the most popular target. We could compile rustc
with support for “newer” instruction architectures, e.g. AVX256, which seems to provide some wins. However, that would mean that the compiler would no longer work on older x64 CPUs that we still officially support! Or we could use a different memory allocator (e.g. mimalloc), which also seems to provide some modest performance wins, but at the cost of increased
memory usage, which could
cause rustc
to go OOM on devices with smaller amounts of RAM. Maybe you’re thinking why can’t we just ship multiple versions of the compiler (for the same target) with different optimizations and then let people (or rustup
) choose which one to use? Well, building the most optimized variant of the compiler for Linux already takes an enormous amount of CI resources right now, not to mention that distributing the toolchain in multiple variants could balloon the amount of data we ship to users over the network, which is already gigantic. There are simply trade-offs everywhere.
I think that to achieve truly significant build performance wins, there are mostly two ways forward. The first one, which I find highly promising, is improving certain compilation workflows. We don’t necessarily have to make the whole compiler faster if we can vastly improve the performance (or reduce the amount of work required) of specific workflows that currently limit the productivity of a large fraction of Rust developers. An example of this is the Relink, don’t rebuild proposal, which has the potential of reducing the amount of compilation that has to be performed when you modify a crate in a large Cargo workspace. Approaches such as this one do not necessarily make the compiler faster per se, rather they make the compilation process smarter, which is an area in which rustc
could be improved a lot. Many of these potential workflow improvements are also not strictly tied only to the compiler itself, but they happen at
the intersection of rustc
and the used build system (most often Cargo).
I hope that some of these ideas will make the most common Rust compilation workflows much faster in the short-to-medium term, without requiring massive changes to the implementation of the compiler. Nevertheless, even such targeted optimizations are not easy to land. Achieving an impressive performance win with a proof-of-concept is the “easy” part. But then comes the long tail of the really hard work - making sure that your change supports all the edge-cases and quirks of the Rust compilation process, works on all targets, doesn’t regress important use-cases and benchmarks, is not a maintenance nightmare, keeps backwards compatibility, etc.
The second way forward, as you might have guessed, is… performing massive changes and/or refactorings to the implementation of the compiler. However, that is of course challenging, for a number of reasons. First, you need to do a “vibe check”6 with the rest of the compiler team with a Major Change Proposal, to make sure that other compiler maintainers are on board with you performing such a major change. Then you will obviously need to implement these changes, which can take a lot of effort. For example, if you change one thing at the “bottom” layer of the compiler, you will then have to go through hundreds of places and fix them up, and also potentially fix many test cases, which can be very time-consuming7. You will also need to find reviewer(s) with bandwidth to approve your changes, which can span over many PRs and weeks, months or even years of work.
Performing large cross-cutting changes is also tricky because it will necessarily conflict with a lot of other changes being done to the compiler in the meantime. You can try to perform the modifications outside the main compiler tree, but that is almost doomed to fail, given how fast the compiler changes8. Alternatively, you try to land the changes in a single massive PR, which might lead to endless rebases (and might make it harder to find a reviewer). Or, you will need to do the migration incrementally, which might require maintaining two separate implementations of the same thing for a long time, which can be exhausting.
An example of a large internal change (albeit one that is not directly related to performance) is the reimplementation of the compiler’s trait solver. Even though it is being worked on by very smart and incredibly skilled people, this is a process that takes several years to finish. That’s the scale that we’re dealing with if we would like to perform some massive refactoring of the compiler internals, and it should set the expectations for people that ask us when are we finally going to rewrite the whole compiler using Data-oriented Design to make it a bazillion times faster.
Speaking of DoD, an additional thing to consider is the maintainability of the compiler codebase. Imagine that we swung our magic wand again, and rewrote everything over the night using DoD, SIMD vectorization, hand-rolled assembly, etc. It would (possibly) be way faster, yay! However, we do not only care about immediate performance, but also about our ability to make long-term improvements to it. If we overfitted the performance using code that is hard to evolve, understand, debug or maintain, it wouldn’t serve us well in the long term. The compiler receives contributions from hundreds of volunteers, and we want to keep the codebase (at least somewhat) approachable to them. So the long-term maintaintability of the codebase is also something that we need to keep in mind.
While the challenges presented above are very real, they are of course not exactly unique to rustc
. The fact that making major changes to a large (compiler) codebase is difficult is probably not very surprising. I’m sure that GCC/LLVM/Clang maintainers also have a lot of ideas on how to improve or speed up things, but it takes a lot of effort and time to get these done. But there are also other reasons why compiler performance is not the only thing we focus on.
Prioritization
Even though many Rust users seem to want compiler performance to be the primary priority of the Rust Project, we should not forget that there are also other priorities that we have to keep in mind.
The Rust compiler is (at least in my opinion) very stable and dependable, and I can count on a new version being released every six weeks. That is no small feat! And we should not take it for granted. It does not happen simply by starting from a working state and then not doing anything. There is so much work that goes into simply making sure that things are not on fire, our infrastructure and CI works, really bad bugs are being fixed in a timely manner, incoming issues are being triaged, backwards compatibility is still being upheld, PRs are being reviewed, security issues are being quickly resolved, we stay on top of changes in external dependencies, such as LLVM, and much, much more. And we have to ensure all of that works on all of our supported targets! At this moment, that’s 8 Tier 1 targets (those have to build and all tests have to pass) and 91 (!) Tier 2 targets (those have to at least build). By a large part,
that work is being done by volunteers who care so much about Rust and its toolchain () and want to keep it working as well as it was so far. There is simply a ton of work to do on Rust9.
And all that work is of course eating away from time that could be spent e.g. on making the compiler faster. If you do not consider that work to be all that important, ask yourself if you would enjoy a twice faster compiler if it randomly started miscompiling your code :)
Another thing is that both the language and the compiler are getting new features all the time. New compiler flags to support projects like Rust for Linux, syntactical improvements, complex language features, and so many other things. In general, the language seems to be far from “done”, and so many things are in process; there are almost 200 open RFCs pull requests, and many other RFCs were accepted, but haven’t been implemented or stabilized yet. At any given point, we have ~700 opened PRs on the main Rust repository, we have over 10 thousand open issues, there’s just so much going on.
And it seems that Rust users want Rust to evolve as fast as it does today, or even faster, which means more changes! When was the last time you “just wanted this small feature X to be finally stabilized” so that you could make your code nicer? It is perfectly fine to want new features from a language, but we should not forget that (amongst other things, such as design discussions) it requires a lot of experimentation, refactoring, bug fixes, testing and implementation effort in the compiler, which again takes time from optimizing performance. Not to mention that adding new features usually makes compiler performance slightly worse, as it tend to do more work, rather than the opposite.
Contributors
In the end, the amount of effort that gets spent on optimizing the performance of the compiler ultimately depends on the people who contribute to it and maintain it. They are often volunteers, and they have diverse interests. Some (compiler) contributors are not interested in doing performance optimizations at all, and that is perfectly fine! Remember, Rust is not a company (this is a great blog post by Mara Bos, I recommend you to go read it!). We don’t tell people what they should work on, nor do we assign them tasks or tickets. If there won’t be people interested in making the compiler faster, it mostly likely will not get done, it’s as simple as that.
That being said, there are certain ways of how we can empower people to work on compiler performance. The Rust Project now has the Project Goals program, which specifies a set of goals and projects that we consider to be important, and we explicitly welcome contributions (and promise to provide reviews and support) in these areas. I’m going to try to make compiler performance be one of the flagship goals in the near future, as I think that it really deserves to be one. That could perhaps help motivate more people to work on it.
Even though it may sound a bit banal, making progress faster is also (at least partially) a matter of funding. Sometimes I hear people complaining about certain projects designed to improve compilation performance taking too long to be ready. It sounds like they expect that there is a fully staffed team of well-paid engineers working on these projects. Well, we should not forget that the Rust Project is still mostly built on volunteers, and many people contribute to it in their free time. Many improvements (including some important compiler performance projects) to Rust were literally driven by a single university student who works tirelessly to make Rust better day by day, without maybe even getting paid for it in any way, and the reason why progress pauses can be (for example) that they must finish university exams or do some actual paid work so that they could afford rent and food.
During the recent RustWeek conference and the associated All Hands event, I saw an increased interest from various companies to invest into improving the performance of the Rust compiler, which is great! Improving the performance of rustc
requires long-term concentrated effort, and providing stable funding for contributors doing that work is definitely one of the ways of how we could make progress faster. However, it is also important that making improvements is not just about doing the implementation work; someone will also need to review it, and then maintain it in the future. In an ideal world, companies would fund long-term ongoing work on the maintenance of the compiler, rather than paying their employees to implement an optimization that scratches their itch and then disappearing. Sometimes, the best way to help something move forward in Rust is not to implement it, but gather knowledge, become an expert in some compiler area, and help review the code of others, so that you can unblock other people to do the actual work :) But that of course takes a long-term investment (both in terms of time and money).
Based on my experience, the interest amongst Rust compiler contributors to work on compiler performance does not correspond to the amount of discourse this topic gets online (and again, this is fine!). If I used the magic wand again, and could persuade more contributors to care about it, would I do it? Probably yes. Would it be a good thing for Rust, long-term? I don’t know! So far, the development of the language and compiler seems to be going fine, and who am I to determine priorities. I trust the people in the Project to collectively do what’s best for Rust, both today and in the future.
One thing that I find interesting is that maybe more people would actually do performance work if they didn’t feel the need to work on so many other things at the same time. Case in point: when I started contributing to Rust back in 2021, my primary interest was compiler performance. So I started doing some optimization work. Then I noticed that the compiler benchmark suite could use some maintenance, so I started working on that. Then I noticed that we don’t compile the compiler itself with as many optimizations as we could, so I started working on adding support for LTO/PGO/BOLT, which further led to improving our CI infrastructure. Then I noticed that we wait quite a long time for our CI workflows, and started optimizing them. Then I started running the Rust Annual Survey, then our GSoC program, then improving our bots, then… Many of these contributions did (at least I hope) affect the performance of compiler in some indirect way, either by unblocking or supporting other contributors or improving our infrastructure (in various meanings of that word). However, only now, after a few years of getting sidetracked, I’m finally trying to get back to doing direct compiler performance work again.
In other words, the more help we can get with maintaining Rust (regardless in which specific area!), the better the chance that we will collectively have more time to improve the performance of the compiler :)
Conclusion
Funnily enough, after I wrote this post, I realized that I could probably replace the term “compiler performance” with pretty much anything else that people want from Rust (“why doesn’t Rust do X”), and a lot of the same reasoning would still apply. In this regard, compiler performance is not that special, it’s just one of (so) many (very important!) things that we care about and that we try to improve to the best of our ability.
In terms of future compiler performance work, I’m hopeful about several initiatives that were discussed at the All Hands events, and also about making LLD the default linker on Linux, which will hopefully finally land in the upcoming months. I’m also planning to run a compiler performance survey soon, so that we can find out what workflows cause bottlenecks for Rust users, and I also want to build better infrastructure for profiling what the compiler is actually doing during the compilation process.
I hope that this blog post provided a bit of insight into why making progress in build performance is not so easy. If you’d like to join the fray and help with it, we would be happy to see you! Let me know what you think on Reddit.
-
I used an old commit so that it can be compiled even with old(er) versions of
rustc
. ↩ -
Luckily, I still had all the stable compiler versions downloaded from my recent compiler errors experiment
↩
-
Sorry, couldn’t resist. ↩
-
At least for some definition of large. The compiler consists of ~600k lines of Rust code, and the standard library has a similar size. ↩
-
I recently saw someone call this vibeck (named after the abbreviated terms typeck and borrowck used in the compiler), which I find super cute. ↩
-
If you are thinking “why can’t AI do this?”, it probably could help with a lot of things in this area, but especially differences in compiler diagnostics (which is a large part of what our test suite checks) often come down to judgement calls that we still (luckily) leave to humans. ↩
-
I like to say that if you gave me a hundred full-time engineers, I would find all of them something to work on in the Rust toolchain immediately. ↩