The Rust compiler isn't slow; we are
blog.kodewerx.org
blog.kodewerx.org
I even found a reddit submission that shows how absurd boost is: https://www.reddit.com/r/cpp_questions/comments/2hzobl/reduc...
Comparing Rust with C here is hard. As others have mentioned, with C, you typically only use libraries that are installed in your system. Rust does not have a global system cache for libraries, and I don't think there is any package manager installing pre-compiled Rust libraries, so at least for the initial compiles, Rust needs to do much more work than C, for Rust dependencies at least. If your Rust project only depends on C libraries, then there is no work to be done of course.
For incremental compiles with C, it is hit or miss. If your C project is properly "modularized" (split into a lot of small enough TUs), then when you change a TU, C only needs to recompile that one TU. In our benchmarks, Rust is faster for that situation, and speculating, this is because the Rust compiler only recompiles the parts of a TU that actually changed.
In practice, however, Rust is much slower than C, because people do not write small Rust TUs, but absurdly huge ones, at least, by C standards. A Rust TU (crate) usually is mapped 1:1 to a C library, which often contains dozens or hundreds of TUs. Just the cost of testing what changed in a Rust crate is often larger than the cost of blindly recompiling the C TU that has a newer timestamp.
If people would split Rust code like they do with C, then this wouldn't happen. But Rust people are too optimistic in that one day the compiler will be able to somehow auto-magically split a crate into multiple TUs as good or even better than what people do for C manually. And do that determination + the incremental compile faster than what takes a C compiler to blindly recompile a tiny amount of code.
FWIW, Nix can do this (via Carnix). But it turns out not to be too useful anyway, because any feature flags that you use will invalidate the cache for everything that depends (transitively) on that crate.
Please avoid misleading statements like "orders of magnitude faster". If that were true, even super-complex projects would compile in a few seconds, given it would be at least 100x faster than compiling C++.
> For incremental compiles, Rust is infinitely faster.
Please avoid obvious exaggerations...
Incremental compiles of C++ take as low as modular is the code. Same for Rust. If you make huge TUs, you get huge incremental compile times.
> C++, even if you use Modules
Modules are not even used yet by any big project nor properly supported yet, and they are not expected to speed up compilation much anyway.
> Comparing Rust with C here is hard.
It isn't hard: C can be compiled way faster than either C++ or Rust because the code and type system is much simpler.
> As others have mentioned, with C, you typically only use libraries that are installed in your system
C code can be anything. C++ libraries are also included with the system.
> Rust does not have a global system cache for libraries
It does have a cache per user/project/etc., which is basically the same and without it the speed would be atrocious.
This does not fit the “well akshually” trope at all.
The tests of my Rust range-v3 like library compile in ~12 seconds. The tests of C++'s range-v3 take ~320 seconds to compile.
> Please avoid obvious exaggerations...
The incremental compile of the same library takes in Rust ~1 second. In C++, it still takes ~320 seconds, zero improvement because C++ header-only generic libraries do not benefit from incremental compilation.
> Modules are not even used yet by any big project nor properly supported yet, and they are not expected to speed up compilation much anyway.
I maintain a ~500kLOC C++ project that uses modules. C++ Modules did not speed up compilation at all over simple PCHs.
> It does have a cache per user/project/etc., which is basically the same and without it the speed would be atrocious.
No it does not. Check out any project from crates.io into a subdirectory, do a cargo build. Check it out into another sub-director, do a cargo build. All dependencies are re-built.
You'd need to go way out of your way and set up like sscache to get something similar, but Rust and cargo do not do this.
> It isn't hard: C can be compiled way faster than either C++ or Rust because the code and type system is much simpler.
Citation needed. On my machine, rustc spends ~80% of its time in LLVM, which is slow, because the translation units are huge. The other 20% is split into ~5% parsin, ~5% type checking, and ~10% LLVM-IR generation.
Even rustc's synthetic trait metaprogramming benchmarks do not spend even 10% of the compile-time in type checking.
So if you have a test for which rustc spends most of its compile-time in type checking, go ahead, and post a link. We'd love to add it to rustc's benchmark suite.
Also, on my machine, while clang spends less time in code generation, it is still over ~60%.
Clang spends more time in parsing and type checking, and that's because C++ overload resolution scales with the number of visible overloads, and the more header files you have, the more overloads there are (e.g. an error with operator<< on a ~500kLOC project produces ~2000 "potential candidates"). While that's part of the slowdown, still 60% of the time is spent in LLVM. The reason is that idiomatic C++ just produces more code than C due to templates, inlining, etc. for similarly-sized TUs.
Parallelizing compilation within a TU is hard to do well. Rustc tries without much success and clang does not. In C, you can simply parallelize by using more compiler processes to compile fully independent TUs in parallel. These TUs are also smaller because idiomatic C code doesn't use many macros to generate code, most functions are linked opaquely, generics also generate few code via void*, and the worst culprit of long compilation times in C, which is including too many header files, can properly be cached using PCHs.
I don't think the same thing would work for Rust because, unlike C++, it doesn't need to constantly reparse files in its depedencies - essentially it already has them automatically. C++ is aiming to move that way in future with modules.
Edit: I just tried turning off precompiled header on one of our larger programs that's heavy on Boost and a couple of our other libraries, and it increased build time from 2:16 to 10:52 (that's minutes and seconds) after fixing a few missing #includes that were covered by the precompiled header.
This is technically true but misleading - the whole nature of C++ means that you can't modularize your project / headers in a way that avoids transitively including lots of headers. There's two reasons for this:
* Lots of Boost (and other library) types are vocabulary types - that's often the whole point of using them in the first place. If my class has a public method that returns a boost::optional<T> or boost::flat_map<K, V> then there's no way to avoid transitively including the relevant Boost headers in my header files.
* Lots of Boost stuff is templated so the implementation has to go in the header, which means the transitive dependencies on other parts of Boost have to be included in the header, even they're only used in the implementation of the Boost class that you're directly use. The heavy templating is a result of the previous point - vocabulary types often need to be templated in order to be useful in many different situations.
* Use libraries like Boost and waste developer time waiting for compilation.
* Avoid libraries like Boost and waste developer time re-implementing functionality (but then you have the same problem anyway) or suffering from not having that functionality available to them.
* Use libraries like Boost and spend a small one-time developer cost turning on precompiled header files in your build system.
You're not getting Threadrippers from these kind of companies.
-> flat_map: https://www.boost.org/doc/libs/1_67_0/boost/container/contai...
-> optional: https://www.boost.org/doc/libs/1_67_0/boost/optional/optiona...
Plus, of course, once they have added that header to their file, that header is included in their file, if you see what I mean, so it may not actually save compilation time while still adding coding effort. Really, I think that technique is only useful if you're only referring to those types (a) in private functions, and (b) by reference or pointer. By the time it's a public function, you have a responsibility to include the full library header in your header file.
Today, I have a medium-sized c++ project that uses Linux, gcc 9.2 and boost and I have yet to find any combination of headers that I can put in the precompiled header file and see any reduction in compile times, either for a full build or an incremental build.
* 8.042s on rustc 1.20.0 from 2017-08-27
* 5.061s on rustc 1.30.0 from 2018-10-24
* 4.789s on rustc 1.36.0 from 2019-07-03
* 3.197s on rustc 1.44.0 from 2020-06-01
But it fails to address how this compares to go or clang of gcc. It mentions those (they take 1/5th the time to compile a similar project), then goes off to explain how it was possible to shave over 75% off the compile time of Rust project, but not if that same trick would shave 75% off a similar go- or c project.
In other words: very good to show us, how, by choosing the correct dependencies compile time decreases. But that does not prove that the Rust compiler isn't slow.
The question does apply to Go, but not C — because the way to reduce compile time for C projects is to just not compile libraries & modules when you don't need to. That's the return from having a stable ABI.
(C++ might be the most interesting in this regard because there's both things - linked / ABI'd libraries as well as header-only "libraries")
[edit: "has" as in "is actually actively used" - of course you can create C-ABI libraries with Go and Rust too, but it's a rather rare practice]
go is extremely aggressive about not recompiling dependencies.
you only pay a long compilation phase the first time you build something. after that its partial compilations all the way.
I'm referring to the fact that there is no "first time build" using a library in C, since C libraries are installed as binaries.
For example on Windows it is quite common to use binary libraries, compiled with the most common options. Additionally COM/UWP kind of work as OS ABI as well.
Same applies to game consoles with their SDKs.
And on other OSes, as long as you just use the official OS SDK compiler, there is a set of binary dependencies that you can just use straight away.
Then the use case of using binary dependencies is so relevant for many corporations using C and C++, that the two major package managers, conan and vcpkg, both support using binary dependencies.
By switching from the default GNU gold linker to LLVM LLD, I have managed to shave off a few seconds on every build. There's an open issue [0] about switching to LLD. It still has some compatibility issues but it works fine on my machine and albeit tiny project.
I would be curious to see how compile times improve for other people just by linking with LLD. You of course need to install LLD first and then you can tell the chain to use it:
time RUSTFLAGS="-C link-arg=-fuse-ld=lld" cargo build
[0] https://github.com/rust-lang/rust/issues/39915> The macOS (Mach-O) backend for lld is broken and abandoned a rewrite from scratch has begun, but it's early days
On macOS they can keep using the bundled linker.
[profile.dev]
debug = false [profile.dev.package."*"]
debug = false
I haven't tested how big the impact of that is, but it should be pretty large for projects with a larger number of dependencies.Edit: I've tested it now on cargo-udeps [1], which has a large crate graph but only little code of its own and there is only a small difference between turning off debuginfo for all crates vs only for dependencies, but a sizeable improvement over compiling in debuginfo of dependencies.
[1]: https://gist.github.com/est31/1bb6e7d6f4be2a23701b8c7c1c678b...
Wait until the author tries Delphi. Then will realize compile times for Go or C are slower then a snail
[0]: https://vlang.io/ .
As to the promised dates that for me is similar to internal company deadlines for software projects - they are almost never on time but as an end-user I would be non the wiser. Amedvednikov's issue here was that he stated his deadlines to the world at large; I have certainly committed the same mistake many times over before I got more conservative re. making any hard deadlines on complex problems & do not feel that means anyone should rightly write off my dev commitment or sincerity re engagement in a project.
almost half of new code is written by the community now
V is very simple to get into, we have 250+ contributors after less than a year
And after it was done you're like "hey look, V can slingshot a stone up to the hill real fast, hence it can be really fast and really cheap to reach the moon too". Tell you what, when you "reach the moon" using V, same as what Delphi does it, and it's going to be faster then come and talk to me, ok?
But I'll also be open sourcing https://gitly.org this week.
This is going to be a second big and official project written in V.
I checked the source code, and found that for URL retrieval was using `wget`, while it was marketing itself as a zero dependencies language. I stopped giving it any consideration immediately.
The wget thing was a temporary hack of course which was gone after a week or so.
The problem isn't that C compilers are "fast" or C++ compilers are "slow". C++ compilers are very fast, but typical C++ projects just throw way too much work at the compiler (by having too much implementation code in headers, while C headers are typically only declarations).
The underlying problem is that C++ makes it easy (and even encourages through bad practices in the stdlib) to add complexity and hide it under "fancy" interfaces, which indirectly leads to code that's slow to compile and results in a bloated compiler output.
It's possible to create C++ projects which compile just as fast, and result in just as small binary code as C projects (basically "embedded C++", but this means giving up most high-level features that C++ added on top of C (such as most of the C++ stdlib).
In short: C makes it hard to add accidental complexity under the hood, while C++ makes it easy. Add a cargo-style dependency manager to the mix, and the "accidental complexity" problem grows exponentially.
$ ~/.cargo/bin/cargo-tree tree --no-indent | cut -f1 -d' ' | sort | uniq | wc -l
61
Oi. I'm not sure this language dependency management thing is such a good idea.Of course, if anybody knows of an existing crate that does that, it'd save me the effort.
What is harder? It is a single command to install most dependencies.
Using a C dependency is super easy from most languages, including C itself, of course.
> binary size and compile times are smaller at the expense of actual time spent writing code.
Nobody writing C professionally is doing that for those reasons unless the library is trivial.
Now I would like to read why is that, and is it only about the "rebuild time" or also about "make time", once the dependency is built and hasn't been changed. If it's about the rebuild time, I agree that it's not important. If it's a penalty for every use of dependency, then for my tastes it does reduce the number of iterations one can do during the fixed development time.
That aside, I don't know why clap in particular was the problem.
Today, we can separate a Rust application into "app" and "lib". Most of the change would happen inside lib and app would only need to be compiled for infrequent changes and releases. This allows one to use all the fancy features of clap and mostly ignore the compile time cost.
It is annoying to separate a Rust application into "app" and "lib" though, so most people do not do it. They instead opt to replace clap with gumdrop. I think both approaches are valid.
On my computer, most of the time the compilation is finished before I have time to release the return key...
And the generated code, while not highly optimized, is largely fast enough to test/iterate.
I think the author failed here.
The article shows the rust compiler is slow but you might be able to mitigate the issue by switching to lighter-weight dependencies or removing dependencies and instead write the parts you need yourself.
These actions are expensive in time spent, involve tradeoffs, and are available only in some cases, so don't generally address the issue of compile times. That is, these are potential mitigations for slow compile times.
But "we" also include the c/c++/rust developers here.
This is a puzzle that is hard to crack.
WHY "system language" developers near always produce the MOST slow languages on earth (C, C++, Rust, ....).
And given time, CONTINUE to keep everything wrong with them?
C/C++/rust are among the slowest languages in actual use today. Is kinda ironic them claim to build "fast" stuff and the tool, itself, is slow like hell.
Is even more sad considering that Pascal mop the floor with everything, decades ago, and still do.
> I believe there is more to it than that. I believe it's more of a human problem than an algorithmic one.
Totally. Somehow, the C/C++ mindset can't build fast languages, not matter how many decades of wasted compile times are behind us.
Is uber funny that you read how certain dude spend A LOT of effort optimizing his code to get a few nanoseconds here and there for the project is building, but somehow, the same love is not give for our core tools.
Is funny that that guy is also me. I waste DAYS trying to cut the build times of my pipeline. DAYS.
Rust is the ONLY lang in this 20 years that make me do busy work of that kind. BTW, I have used more than other +12
--
Before get angry, remember that Pascal, ADA, Go, exist. And them are orders of magnitude faster than C/C++/Rust. This is not a problem of "but c/rust do that stuff and must be slow", no... is because them, BY DESIGN, are suboptimal tools.
--
Then why you use rust? Because, in contrast with C/C++, at least give a lot of extra benefits that are not present elsewhere (with the exception of ADA).
We can love AND hate something, at the same time. Funny humans.
And C/C++/Rust not have GC... and still ARE slow to compile.
The article was a let down at the end. Yes, less code and less dependencies equals quicker builds. No kidding. It does not deal with Rust itself, however.
But at commercial scale this is a real concern. We're a big C++ shop ... but for reasons that cannot be blamed solely on C++ itself, I quit C++ for the last 18 months for GO weary of the 4-day long build times, the huge executable sizes, and fighting the C++ compiler. The only thing worse than a C++ compiler in a bad mood is Oracle's PL-SQL parser (dumb and silent) or when your wife is mad at you.
Problems committed by programmers:
- builds do not use PCH so C++ builds are indeed slow
- very old legacy libraries have circular dependencies and sorting that out is annoying
- certain important, hard to avoid legacy calls hit into a library chain of dependencies that quickly lead to very large executable sizes
- linking depends on .a files because having tasks link .so files is unreliable in production. Too many tasks, too many libraries, too many versions, too many deployments just too much lead to dependency mismatches leading to cores ... We still link archive libraries not shared but over the last years build teams have taken making the archive files so compiling is usually limited to app code plus whatever self-owned library code was modified. (Not talking language or OS libraries here; that's disciplined. Talking app libraries & tasks for which there are 10000s).
Thus on the whole we can blame the organization not C++ for not being as good as it should at commercial level, very large scale software management. Getting rid of legacy code or refactoring it is another weakness: for the above reasons the effort is often big, and there's always so much new work to do making management of resources tough.
We can blame C++ for other things however:
- the language is far too complicated
- the language involves some duplication of effort between headers and source files
- C++ inherits C's problems with #defines, macros, and other nasty things that break builds at scale with bad things in global scope. Just having a pre-processor is bad. C++ maybe should have been link compatible with C but it dragged in the whole edifice. Other OOP languages were smarter.
- C++ errors arising out of templates or certain other STL areas produce messages which are 2K+ each. It takes time to see what the hell when wrong. C++ often confuses you with the details. I hope GO generics does NOT do that.
- Making code allocator aware and capable i.e. to make things behave properly for performance, memory testing when everything should use a non-default allocator, debugging, and so on is real effort. Memory errors are not that easy to track down in C++ for something that appears to care about safety.
- C++ language complexity drifts into design complexity immediately. If you're <5yrs in C++ you can't really attack a serious project without having Meyer's C++ books side by side.
- A lot of what makes a C++ class or method "good" i.e. narrow contract, assertion checking, unit testing comes, if it comes at all, from the organization's engineering culture not from C++ itself. In other languages it's more baked into the language.
Of course GO avoids many of these problems by design to say nothing of quick build times. But GO built on 20 years of industrial experience while others created it along the way.
Other OOP languages may be better, but sorry the die has been cast. Ditching C/C++ for something better will still likely need a back door to C because there's too much of it to drop it entirely. Those working close to the kernel will need a C backdoor as well.