The Rust compiler has gotten faster again(nnethercote.github.io)
nnethercote.github.io
The Rust compiler has gotten faster again
https://nnethercote.github.io/2021/11/12/the-rust-compiler-has-gotten-faster-again.html
141 comments
I really like Rust, it hits a lot of sweet spots for me – strongly typed, neat type system, the mild functional-ness thats useful while keeping people from going too crazy with functional patterns, usually low on boilerplate, static binaries, clever little things like ? ... but sometimes I wish there was a garbage-collected version of it, the constraints of the borrow checker make things like tree structures at least an order of magnitude harder to get right, and I'd love to have the ergonomics of the language and the ergonomics of a gc-ed runtime. Not going to happen I guess, but I feel like there's an opportunity for a Rust-ish language that is somewhat less finnicky, at the cost of making some abstractions more expensive, for microservices with complex business logic and such.
I actually support the idea of adopting Rust's ideas in a memory-safe, garbage collected language. I like garbage collection—it's not always the right tool, but it's a great tool in the proper domains. Modern GC builds upon decades of research to achieve memory safety at runtime with good performance and very low cognitive overhead on the part of the programmer.
What I don't agree with is the idea of taking Rust's ideas and putting them into manually-memory-managed, unsafe languages. We should be moving away from such languages as an industry.
What I don't agree with is the idea of taking Rust's ideas and putting them into manually-memory-managed, unsafe languages. We should be moving away from such languages as an industry.
I can attest that so far the lifetime static analysers for C++ haven't progressed that much.
However until certain C++ overloads don't give more love to memory-safe, garbage collected language or languages like Rust, that is better than nothing.
For example, I don't expect Windows team to ever lose their love for C++, NVidia moving from their C++ love for CUDA, or Metal shaders in something else.
Ah, and then many of those languages are keeping C++ around by building on top of GCC or LLVM.
So any improvement towards to make C and C++ safer is better than just using them as they are.
However until certain C++ overloads don't give more love to memory-safe, garbage collected language or languages like Rust, that is better than nothing.
For example, I don't expect Windows team to ever lose their love for C++, NVidia moving from their C++ love for CUDA, or Metal shaders in something else.
Ah, and then many of those languages are keeping C++ around by building on top of GCC or LLVM.
So any improvement towards to make C and C++ safer is better than just using them as they are.
> I actually support the idea of adopting Rust's ideas in a memory-safe, garbage collected language.
sooo... ocaml?
sooo... ocaml?
Or many other programming languages, but yes, OCaml is a pretty good one, and it is perfectly suitable for systems programming.
> I actually support the idea of adopting Rust's ideas in a memory-safe, garbage collected language
You're describing Swift
You're describing Swift
Swift also has some stuff coming from the history of Objective-C interop (inheritance is the big one I'm thinking of, although I'm sure there are others that are slipping my mind). I'm not sure how mainstream this opinion is, but personally I _prefer_ not having classes and inheritance, and while I could always choose not to use them in my own code, I imagine it's common enough in libraries that I'd end up dealing with it a bit anyways. Combined with the fact that Linux support still seems like it's a bit secondary makes me feel like there still is a potential niche out there for "Rust with garbage collecting" that Swift might not be filling entirely.
Yeah I agree, I think one of the biggest mistakes in Swift was coupling reference types to classes. Choosing whether something is a reference type of value type should have been orthogonal to the class system, if they included classes at all.
But in practice I think modern swift uses inheritance very sparingly, and leans much harder on the protocol/extensions system, which is very well design imo
But in practice I think modern swift uses inheritance very sparingly, and leans much harder on the protocol/extensions system, which is very well design imo
What are you both talking about? Swift doesn't have garbage collector the same way Rust doesn't have it.
Swift doesn't have tracing GC, but to be pedantic automatic reference counting is a form of GC.
But that only goes for reference types. For value types you're correct that both languages use mostly static memory management.
I guess in that sense you'd have to consider Rust as a gc'd language as well: the only difference really is that you have more fine-grained control over which mode of reference counting is being used for a given object.
But that only goes for reference types. For value types you're correct that both languages use mostly static memory management.
I guess in that sense you'd have to consider Rust as a gc'd language as well: the only difference really is that you have more fine-grained control over which mode of reference counting is being used for a given object.
See also "A Unified Theory of Garbage Collection", https://researcher.watson.ibm.com/researcher/files/us-bacon/...
> We present a formulation of the two algorithms that shows that they are in fact duals of each other. .... Using this framework, we show that all high-performance collectors (for example, deferred reference counting and generational collection) are in fact hybrids of tracing and reference counting.
...
> For every operation performed by the tracing collector, there is a corresponding "anti-operation" performed by the reference counting collector.
...
> We have shown that tracing and reference counting garbage collection, which were previously thought to be very different, in fact share the exact same structure and can be viewed as duals of each other.
> We present a formulation of the two algorithms that shows that they are in fact duals of each other. .... Using this framework, we show that all high-performance collectors (for example, deferred reference counting and generational collection) are in fact hybrids of tracing and reference counting.
...
> For every operation performed by the tracing collector, there is a corresponding "anti-operation" performed by the reference counting collector.
...
> We have shown that tracing and reference counting garbage collection, which were previously thought to be very different, in fact share the exact same structure and can be viewed as duals of each other.
Chapter 5 of the GC Handbook, one of the major CS references in GC algorithms.
GC is standalone runtime that does tracing and deallocates memory at some arbitrary time. Are you really claiming that using C++'s smart pointers is using GC? I think you're conflating semi/automatic memory management with GC.
You're conflating tracing GC algorithm with GC as CS concept.
Regarding C++ smart pointers, if you manually call them like in std::shared_ptr<>(), no.
If the runtime calls it for you like on C++/CX or compiler blessed types like _com_ptr_t, then surely.
Regarding C++ smart pointers, if you manually call them like in std::shared_ptr<>(), no.
If the runtime calls it for you like on C++/CX or compiler blessed types like _com_ptr_t, then surely.
ARC in Swift doesn't have runtime part, retain/release code is injected at the compile time. It doesn't have ie. cycle detector running periodically or anything like that. If you define this setup as GC then by this definition Rust's borrow-checker is also a GC because it works the same way - by injecting static code during compilation (but has different rules).
Swift does not employ garbage collection, but reference counting.
Chapter 5 of the GC Handbook, one of the major CS references in GC algorithms.
Reference counting is a GC algorithm, regardless how people sell it to the man on the street.
Additionally there is a whole set of politics how ARC had to be sold after the technical failure to make Objective-C GC implementation work without core dumps, due to the underlying C semantics.
Plus making retain/release calls automatic wasn't anything new as idea, VB and Delphi did it first with COM AddRef/Release.
Reference counting is a GC algorithm, regardless how people sell it to the man on the street.
Additionally there is a whole set of politics how ARC had to be sold after the technical failure to make Objective-C GC implementation work without core dumps, due to the underlying C semantics.
Plus making retain/release calls automatic wasn't anything new as idea, VB and Delphi did it first with COM AddRef/Release.
Swift doesn't have tracing gc but ref counting is a form of gc
Also Haskell, D, Chapel, among others.
The constraints of the borrow checker do not make tree structures harder to get right! They just force you to get it right!
I don't think Rust is low on boilerplate.
As an example, here is a video of David Pedersen creating a simple Tower service: https://www.youtube.com/watch?v=16sU1q8OeeI&t=945s
I found it intimidating how much you have to write to create a simple middleware.
As an example, here is a video of David Pedersen creating a simple Tower service: https://www.youtube.com/watch?v=16sU1q8OeeI&t=945s
I found it intimidating how much you have to write to create a simple middleware.
There's some amount of boilerplate that is unavoidable, but, if you have the patience, `macro_rules!` type macros can help cut down on syntactical boilerplate in many cases.
Compared to C, and sometimes C++, there's generally less boilerplate in Rust. Compared to more traditional web languages, though, Rust will seem to have more boilerplate due to it's lower-level design.
Compared to C, and sometimes C++, there's generally less boilerplate in Rust. Compared to more traditional web languages, though, Rust will seem to have more boilerplate due to it's lower-level design.
They already exist, OCaml, Haskell, F#, Scala, Swift.
Totally agree, really wish for a language that has all rust features + GC. OCaml and F# come close enough but they are not as popular.
For what is worth, you can opt-into reference counting and internal mutability with container types (Arc/Rc, Cell/RefCell, RwLock/Mutex), and get the same performance characteristics as a GC language (but without a JIT) for the cost of more complex types in your signatures.
Edit:
> the constraints of the borrow checker make things like tree structures at least an order of magnitude harder to get right
The constraints of the borrow checker make those things more unlikely to compile without extra ceremony. The likelihood of getting them right is orthogonal from making the compiler "happy", even in Rust but specially in other languages. The way I've heard it described is that Rust is a language where you get the hangover first. It upfronts dealing with the error conditions of your problem space in a way that (you can argue) hinders the exploration phase, but that makes of a very quiet maintenance phase.
Edit:
> the constraints of the borrow checker make things like tree structures at least an order of magnitude harder to get right
The constraints of the borrow checker make those things more unlikely to compile without extra ceremony. The likelihood of getting them right is orthogonal from making the compiler "happy", even in Rust but specially in other languages. The way I've heard it described is that Rust is a language where you get the hangover first. It upfronts dealing with the error conditions of your problem space in a way that (you can argue) hinders the exploration phase, but that makes of a very quiet maintenance phase.
> and get the same performance characteristics as a GC language
Pretty sure modern state of the art GCs, like the ones found in Java or .NET runtimes, are way more efficient than reference counting.
Reference counters have that unfortunate RAM access pattern where the counter is frequently updated from multiple threads concurrently. On modern processors, this means concurrent access to a cache line by different CPU cores. These cache coherency protocols are pretty slow. On many CPUs, reading a cache line recently modified by another core costs 300-500 clock cycles, even more expensive than a cache miss and roundtrip to system RAM.
Pretty sure modern state of the art GCs, like the ones found in Java or .NET runtimes, are way more efficient than reference counting.
Reference counters have that unfortunate RAM access pattern where the counter is frequently updated from multiple threads concurrently. On modern processors, this means concurrent access to a cache line by different CPU cores. These cache coherency protocols are pretty slow. On many CPUs, reading a cache line recently modified by another core costs 300-500 clock cycles, even more expensive than a cache miss and roundtrip to system RAM.
Python is experimenting with a novel approach where objects have two reference counts, one exclusively for the owning thread and another for all other threads, improving performance when most of the increments/decrements are done by the owning thread.
https://lwn.net/Articles/872869/
https://lwn.net/Articles/872869/
They are, specially when ARC marketing from Apple gets validated.
https://github.com/ixy-languages/ixy-languages
https://github.com/ixy-languages/ixy-languages
People like to bring up reference cycles a lot as if it's always a deal-breaker, but for short-lived processes it kind of seems like a non-issue. Anecdotally, we've seen Instagram get overall gains by disabling GC in Python (which will still do reference counted cleanup).
Exactly, I like implementing compilers and I personally never bother with anything other than reference counting nowadays. Ref count gives a very easy to reason semantics. So I count as a feature in my language even if it's slower than GC. Moreover, some of the "memory leak" discussions nowadays miss the biggest issue behind memory leak, which is resource allocation. Some people measure memory leak as the # bytes that process didn't deallocate at the time of exit.
A Rust team alum has actually written a bit about the topic:
https://without.boats/blog/notes-on-a-smaller-rust
https://without.boats/blog/revisiting-a-smaller-rust
https://without.boats/blog/notes-on-a-smaller-rust
https://without.boats/blog/revisiting-a-smaller-rust
Sounds like you're looking for Nim!
>> Not going to happen
I sure hope not. Borrow checker has little to do with memory allocation/deallocation. It's there so that you can't have unprotected mutable shared state.
I sure hope not. Borrow checker has little to do with memory allocation/deallocation. It's there so that you can't have unprotected mutable shared state.
Kudos to the Rust and LLVM teams for taking performance so seriously! I'm excited to see things get even faster once the new LLVM pass manager is enabled by default.
That improvement is already included in the newest nightly, so it is included in the linked benchmark. It will ship to stable in the 1.57.0 release:
https://github.com/rust-lang/rust/pull/88243
Rustc supports running with LLVM versions other than 13.0, which some distros enable, but the official builds of rustc all use LLVM 13.0, a pre-release version in 1.56.0, and the final in 1.57.0 onwards.
https://github.com/rust-lang/rust/pull/88243
Rustc supports running with LLVM versions other than 13.0, which some distros enable, but the official builds of rustc all use LLVM 13.0, a pre-release version in 1.56.0, and the final in 1.57.0 onwards.
Anyone using Rust on ESP32? I'm 24 hours into trying and it's dredging up memories of old cross-compile toolchain problems, like trying to compile dependencies as x86 :). Either way, it's a fun project so far so I'm not complaining. Embedded seems like a great fit for Rust for ensuring correctness and speed.
If you haven't yet, check out Scott Mabin's blog. He's working on ESP32 & Rust: https://mabez.dev/blog/posts/esp-rust-18-10-2021/
LLVM (Rusts' backend) doesn't support the Xtensa instruction set, which makes this so complicated. The newer ESP32-C{3,6} instead use a RISC-V instruction set which is already supported by Rust, so its much easier to set up.
So far I managed to get basic serial coms working, but am waiting for some HAL so I can build a blinky LED.
So far I managed to get basic serial coms working, but am waiting for some HAL so I can build a blinky LED.
No, I started using Nim on esp32's due to the Rust/llvm toolchain issue and found it quite nice. I wrapped much of the esp-idf in a project called Nesper. Though I keep an eye on Rust in the embedded world. Rust type system does seem to require a lot more work creating shared hardware api's.
I remember Pascal compiling crazy fast compared to C/C++. Partly because the language was designed to be easy to parse, but I assume part of the speed came at the cost of producing less optimal code.
But if most of your compiles are during development and debugging where optimization is less important, wouldn't it be nice to have super fast compilation during those stages? I don't know if any other compiled language comes close to Pascal in that regard.
But if most of your compiles are during development and debugging where optimization is less important, wouldn't it be nice to have super fast compilation during those stages? I don't know if any other compiled language comes close to Pascal in that regard.
https://en.wikipedia.org/wiki/Tiny_C_Compiler probably compiles as fast or faster than the Pascal you remember.
You can even use it as a backend for the Nim (https://nim-lang.org/ ) compiler for subsecond builds of a modern language with various choices for automatic memory management.
You can even use it as a backend for the Nim (https://nim-lang.org/ ) compiler for subsecond builds of a modern language with various choices for automatic memory management.
> I don't know if any other compiled language comes close to Pascal in that regard.
Go and D are pretty comparable.
Go and D are pretty comparable.
A faster codegen backend for debug builds is in the works.
https://github.com/bjorn3/rustc_codegen_cranelift
https://github.com/bjorn3/rustc_codegen_cranelift
Will it allow incorrect code like an interpreter? I edit one controller/view in Django, print something and let it crash. Then I fix it and (hopefully) correct the rest of the controllers.
All languages allow incorrect code that crashes. Perhaps you're referring to compiler-aided refactoring (make a change, compile, inspect the errors, correct, repeat), which yes, Rust is very good at, as are most statically-typed languages.
A big part was that pascal had a decent module system, while C only has includes. The same header files can be included from multiple places and have to be processed multiple times. IIRC the Plan9 C compiler had some restrictions (or maybe just conventions) to prevent this.
Also, Borland Pascal / Delphi compilers weren't terrible at optimizing. And they often produced smaller executables.
Also, Borland Pascal / Delphi compilers weren't terrible at optimizing. And they often produced smaller executables.
From my experience C is very fast to compile. Especially compared to C++, because of the metaprogramming and monomorphization stuff. In C you would use void*.
Dynamic dispatch is faster to compile than static dispatch, but runtime performance differs. You can do dynamic dispatch in Rust, I think it could enable faster/smaller builds but at the cost of worst runtime performance.
Tradeoffs, Yada Yada.
Dynamic dispatch is faster to compile than static dispatch, but runtime performance differs. You can do dynamic dispatch in Rust, I think it could enable faster/smaller builds but at the cost of worst runtime performance.
Tradeoffs, Yada Yada.
On our C projects back in the 2000's, a full build took around one hour, composed of Apache, IIS ISAPI modules, our own mod_tcl fork, all the TCL database drivers for MS SQL Server, Sybase SQL Server, Informix, Oracle, and several other native modules.
Then we had to repeat it for HP-UX, Aix, Solaris, Windows NT/2000, in release and debug variants.
A release would be a day event, so fast to compile is relative.
Then we had to repeat it for HP-UX, Aix, Solaris, Windows NT/2000, in release and debug variants.
A release would be a day event, so fast to compile is relative.
While I agree the common pattern is to use void*/dynamic dispatch, this is not necessary. E.g., https://github.com/glouw/ctl/ or https://github.com/c-blake/bst show a couple ways to have generic code statically specialized in regular old C. (Instantiation/specialization is just marginally less automatic/syntactically supported.)
Wow, this is super impressive. Great work rust team!
Nice to see the ongoing improvements, congratulations to everyone making it happen.
arthurcolle(14)
hvasilev(11)