I seriously hope the Rust community won't get used to these slow compile times the way the C++ community got used to it. C++ has some really big flaws that can cause compilation time to become unbearable but for some reason boost really loves to abuse these flawed features. Boost is almost as bad as npm. You include a single header and then you end up including thousands of other boost headers. When I looked at the include hierarchy of a single cpp file 99% of the includes were from boost and maybe 30 includes were actually from the C++ project itself. I'm sure you could cut down 80% of the compilation time simply by removing boost and replacing it with different libraries.
On the bright side, the compiler's performance is improving constantly. Timing the compilation of lewton plus its dependencies on the 0.9.4 release commit:
In my experience, Rust compiles orders of magnitude faster than similar C++ code for initial compiles. For incremental compiles, Rust is infinitely faster. C++, even if you use Modules, needs to re-compile, re-instantiate, re-check all the templates all the time. I speculate that this alone is what probably is responsible for the largest difference in compile-times.
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.
> Rust compiles orders of magnitude faster than similar C++ code for initial compiles
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.
> 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++.
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.
This type of comment is the worst. What did you add to the conversation besides a bunch of useless "well ackshually"? If you want to respond to a comment, respond to it in full, not only conveniently out of context sentences.
> 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.
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.
crate2nix is better maintained alternative to carnix: https://github.com/kolloch/crate2nix
The cache is still useful though if you think about CI builds or having a common project in a team.
For C++, if you have many library headers used repeatedly in many source files, the number 1 suggestion ought to use a precompiled header (which you write yourself with commonly used library headers but none of your application headers). It was mentioned on that page but a bit buried and OP replaced a whole dependency before trying it.
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.
It seems to depend a lot on the particular compiler. I know that in the past, precompiled headers made a huge difference with MSVC (and maybe they still do, but I haven't used it in some years at this point).
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.
Hmm, that is interesting... You're absolutely right, the program I was compiling is a Visual Studio program. (The code is actually cross-platform, partly thanks to the use of Boost, but for historical reasons it only has a Windows build system in the form of Visual Studio projects.) I'd definitely like to try my experiment again with GCC or clang at some point.
Precompiled headers are a workaround when you can't modularize your project / headers files. But to use them you commit to the wrong way, including everything the same way everywhere.... what you should do is include only minimal stuff from the headers, and prefer to include most dependencies only in implementation.
> Precompiled headers are a workaround when you can't modularize your project / headers files.
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.
That's true, but it creates an annoying extra burden on anyone that calls those functions to include the full-fat library header themselves. Since they might simply write `auto x = my_obj.returns_optional()`, it might not even be that obvious they need to add that header.
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.
Indeed, but it's just ignorance driving that. You end up with three choices:
* 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.
Boost is a pain to compile, sure, but a bit of a necessary evil for anything C++. Threadrippers are dirt cheap compared to developer time and compilation is embarrassingly parallel. Just by devs 32-core boxes and be done with it.
Except that on the projects I have been consulting, the last time I actually coded on a desktop was around 2006. And most customers nowadays just have IT throw a VM to externals.
You're not getting Threadrippers from these kind of companies.
The article does a good job explaining why a certain project might have a long compile time: dependencies.
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.
> but not if that same trick would shave 75% off a similar go- or c project.
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]
How C++ deals with it depends on the project and OS.
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.
I have been tinkering with Rust lately and compile times have been an issue since almost the beginning. After searching around for a bit, I discovered that a significant amount of time is spent in the linker, not in the actual compiler.
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
Most of the stuff your linker has to churn through is actually debug info. If you turn it off, you'll speed up linking whether it's with LLD, gold, or ld.bfd. Do this in Cargo.toml:
What if you don't really program in Rust yourself but still cargo install the cool CLI tools that people have produced in it? Is there something I can tweak in my global settings for speeding that up?
As the sibling comment says, you can always turn it back on when you want to do debugging. Personally, I mainly do printf debugging so I don't miss it much. Also there is the option of only turning it off for dependencies like:
[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.
Quote: "There is plenty of evidence that suggests rustc is slow relative to compilers for other languages; Go can compile applications with similar complexity in 1/5 the time, and clang or gcc can do the same for applications written in C"
Wait until the author tries Delphi. Then will realize compile times for Go or C are slower then a snail
I have a project for a client. Nothing much, just your ordinary database driven application, customized for his needs. I use some external libraries from DevExpress (for showing data), from ReportBuilder (german one) to show different reports, etc etc. All in all, since I have sources for all as well, it's around 1 million lines of code. The application itself is just a measly 10k lines of code written by me in past month. So, when I do a "build all" in Delphi, it will go and compile all external libraries and it shows me the lines of code they compiled. Mind you, real lines of code, not empty lines, and it does not include interface/types declarations, just actual code lines - you know, the ones where you can actually put a breakpoint if you want to also debug it.
Since my style is to have for each project its own virtual machine, this compiling runs inside the virtual machine, and the files that said virtual machine consist of are sitting on a HDD with 5000 rpm (old cheap style HDD, not an SDD mind you).
Takes 45 seconds to do that "build all".
Do you have an equivalent V project size to tell me your compile time? For a real application?
So another programming language the type "dozens for a dime", that somebody in the middle of their tenure at Uni, out of boring thought "you know what this world is lacking? another programming language".
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?
Been toying with vlang for a while now and may well use it in my next side-project - honestly feel to me like the modern C I could have dreamt up. Especially when the standard cross-platform UI (https://github.com/vlang/ui) comes of age it will have serious advantage on most other languages I know of.
It looks like it could be that, but I have trouble trusting the author. He makes a lot of promises he doesn't keep. The more minor ones are things like release dates that have been pushed back month after month, but for instance volt has been abandoned for almost a year. I'm starting to think of v as more of a real thing after this much steady development (even if release dates are meaningless), but probably wouldn't trust a project to it until the community could sustain development if amedvednikov got hit by a bus tomorrow.
From my perspective the project has progressed faster in years than many projects do in a decade so development is certainly happening rapidly.
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.
I remember seeing this some months ago when the source code went open.
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.
I had the same experience going from C++ back to C. I was surprised how fast my C code compiled, and how small the resulting executables were, even though I already tried to avoid the worst offenders in my C++ code.
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.
You could say that Rusts great dependency management is both a blessing and a problem at the same time. C programmers, often motivated by resource constraints, are much less likely to use third party dependencies - not just to save resources but also because it is just much harder to do it and to do it well. They end up just rewriting the parts they need themselves. Because those parts are likely a small subset of a full-featured library, binary size and compile times are smaller at the expense of actual time spent writing code.
Do we also need to add the time tracking down issues and dealing with all the licensing and churn issues in mostly unstable third party rust dependencies?
I'm about to ship my first Rust project for a client that actually cares about licensing stuff. It's trivial to add a new line to Cargo.toml, but can you legally redistribute it? What MIT-style copyright lines do I need to ship in our License file? Well, let's list the project dependencies and find out how many packages I need to go examine:
I'm working on a crate that'll build a String satisfying all the license requirements of a Cargo project's dependencies – you should be able to put that in a build script, include_str!() the file that the build script spits out, then display that string in the About box (like the convention of Windows Forms programs).
Of course, if anybody knows of an existing crate that does that, it'd save me the effort.
The way I read it, the conclusion is that Rust compiler is indeed slow when there are dependencies, even as "simple" as argument parsing?
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.
Clap (the argument parser in discussion) does a little more: parsing to correct types, colorful output, generation of shell completions and man pages, fuzzy matching (did you mean X?) and so on.
That aside, I don't know why clap in particular was the problem.
I consider clap is a different type of dependency from lalrpop and regex. Clap is part of the application interface and most likely does not change that often. Lalrpop and regex are part of the domain logic and probably change fairly often.
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.
To make fair comparison, you could translate C code very literally to Rust code, not using any "advanced" features or different libs (meaning no_std). Then you could see apples to apples how it'd compare to clang. Probably slower still, but at least you'd know that the slowness wouldn't be due complex generics or macros or something.
There is an experiment that anyone interested in compiler speed and the joy of fast iteration programming should do : trying a real fast C compiler: TCC (from Fabrice Bellard)
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.
> ...this is a dissent to a common criticism that the compiler is slow. In this discussion, I will argue that these claims are misleading at best.
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.
The systems languages you reference have to do memory management by design. It's very difficult to write a scalable, maintainable, performant operating system or high-performance application in Pasca, ADA, or Golang. While they are all decent, I'm referring to "low-level" as in device drivers or "high-performance" as in game engines. Aside from some toy microcontrollers running go or some userland fuchsia stuff, there's a reason serious stuff still uses those languages. They're slow because they intentionally trade compile time for run-time performance. If I write in such a language, I've got a pretty good idea of how it will run; I don't have to worry about "Oh no, what's the GC gonna do now."
I was talking to my wife about why something around the house was slow to get done. Well, Rust wasn't having that. For a full 30-mins I was on full defense trying to figure out how Rust got into the house. It's everywhere. And it likes to talk! I'm gonna have to get to know it better, because it seems I have no choice. Ok, satire out of the way, let's get to the real points.
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.
I even found a reddit submission that shows how absurd boost is: https://www.reddit.com/r/cpp_questions/comments/2hzobl/reduc...