I even found a reddit submission that shows how absurd boost is: https://www.reddit.com/r/cpp_questions/comments/2hzobl/reduc...
I even found a reddit submission that shows how absurd boost is: https://www.reddit.com/r/cpp_questions/comments/2hzobl/reduc...
* 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
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.