6 ms·
Rust: Zero-Cost Abstraction in Action
- tiziano88 7y agoThere is nothing about zero const abstractions in this article, just basic compiler optimizations.
- Ericson2314 7y agoWell, functions are abstractions too, but yes to me the interesting part of "zero const abstraction" is "zero cost data structure composition", and that indeed is not covered.
- kibwen 7y agoThe OP doesn't explicitly demonstrate any zero cost abstractions, however these "basic compiler optimizations" are only feasible to achieve statically (i.e. without a JIT) in the presence of sufficiently transparent abstractions, which takes a good deal of language design effort to enable.
- danieldk 7y agoIterators in Rust are (usually) zero-cost abstractions of loops. Bjarne Stroustrup's definition: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better. Rust iterators fit in the second part.
- Ericson2314 7y agoThe earlier transformations are actually more impressive. Constant folding is almost always has a good RoI, so lots of compilers do it, and it's simple because, well, it's a constant there is no variables or partial eval needed. The others require lots of inlining before the final rule fires, and so are more ambitious. Seeing what pub fn sum3(n: i32) -> i32 { (1..n).sum() + (1..2*n).sum() + (1..(n + 2)).sum() } does would be more interesting to me. Also, while all the inline is rustc, I assume the "triangle number trick" is LLVM.
- SAI_Peregrinus 7y agoGodbolt supports Rust, and can show the LLVM IR: https://godbolt.org/z/GsccW3 https://godbolt.org/z/GsccW3
- pjmlp 7y agoA bit offtopic, I love how Godbolt grew out of C++ community to embrace as much AOT toolchains as possible, kudos to Matt and everyone involved into making it happen.
- incadenza 7y agoAre compiler optimizations like summing a series done on an ad-hoc basis? Certainly the compiler couldn’t have discovered or inferred (not sure what term to use) that formula, no? Just generally curious.
- kibwen 7y agoThose optimizations are happening on LLVM's end (though the higher-level language will have to expose sufficiently transparent abstractions to make these optimizations possible). I'd love to read a book on the optimization techniques that are implemented in LLVM/GCC to make transformations like this possible.
- incadenza 7y agoGotcha. Thanks. So presumably Clang would have done the same for C?
- danieldk 7y agoEasy to check, thanks to godbolt: https://godbolt.org/z/SopXnf https://godbolt.org/z/SopXnf
- steveklabnik 7y agoYes, and it's mentioned in the post that the C code was also brought into parity.
- incadenza 7y agoYeah I saw that, but just wasn’t sure how using intrinsics played a role.
- exacube 7y agoAd-hoc basis. Usually comes out of studying the most common classes of computation and optimizing for it. For example, the "summing a series" probably falls out of loop optimizations: https://en.wikipedia.org/wiki/Loop_optimization https://en.wikipedia.org/wiki/Loop_optimization
- yahyaheee 7y agoThis is neat but there is still cost in compile time
- JoeCamel 7y agoUsually in Rust community zero cost means zero runtime cost. Obviously, there are many other costs you could define.
- lasagnaphil 7y agoThis isn’t really talking about Rust, it’s actually talking about the optimization capabilities of LLVM (the compiler backend, which quite a lot of languages use, such as Clang for C++, Rust, Swift, Julia, Zig, ...) These languages all have similar chances of performing those same optimizations, at least in those simple cases. What I’m interested is how the IL code for the compiler frontends for each of those languages are more well-optimizable for LLVM (in practical situations, not just a few lines of simple numerical code.) I’ve heard that you need to be careful about encoding IL code in the right way such that LLVM does not generate needless memcpy’s or something but I’m not that much of a compiler expert...
- kibwen 7y agorustc does indeed deliberately seek to emit LLVM IR that resembles what Clang would emit, in order to benefit from the same sorts of optimizations that default LLVM is tuned for.
- kbenson 7y agoIt's both, it's just complicated by the fact that LLVM is optimizing it to a known formula. The zero cost abstractions are the fact that you get the same output, special optimization and all, when you do the original loop, or when you use the "(1..n).fold(0, |x,y| x + y)" variant, or "(1..n).sum()" variant. Rust is converting those to intermediate code that is the same, or close enough, that LLVM is able to apply the same optimization. Would it be better is some sample code was chosen that wasn't special cased to the degree that the entire algorithm was replaced wholesale? Probably. It doesn't invalidate the premise though, just slightly obscures it.
- SeekingMeaning 7y agoFor anyone interested in reading more about this, I would recommend Zero Cost Abstractions[1] by withoutboats, who is a contributor to Rust. 1: https://boats.gitlab.io/blog/post/zero-cost-abstractions/ https://boats.gitlab.io/blog/post/zero-cost-abstractions/
- drej 7y agoThis is not Rust specific, this is a compiler thing, both LLVM and GCC can detect sums and generate closed form formulas instead. There are other fun algorithm detections - e.g. if you try to do bitcounts yourself, LLVM will use popcnt instead. Compilers are awesome, check out Matt Godbolt's talk on this very topic: https://www.youtube.com/watch?v=nAbCKa0FzjQ https://www.youtube.com/watch?v=nAbCKa0FzjQ
- pkilgore 7y agoThe ocaml compiler does constant folding too, and I consistently look at the (usually javascript because of Bucklescript) output in amazement when it finds shit like that. Unrolling all my tail recursion is great too.
- pjmlp 7y agoFor anyone that wants to learn about optimizations in AOT compiled ML languages, have a look at: "The Implementation of Functional Programming Languages" "Compiling with Continuations" "Modern Compiler Implementation in ..." (C, Java and ML variants) Although oriented towards Lisp, "Lisp in small pieces" is a classical book as well, with many optimizations like inlining of lambda calls across multiple call levels.
- bdd 7y agoThese are not about abstractions but compile time optimizations. These are also not implemented in the Rust compiler but LLVM. So any any language frontend in front of LLVM would yield the same optimizations. According to Wikipedia the list is: > [...] variety of front ends: languages with compilers that use LLVM include ActionScript, Ada, C#, Common Lisp, Crystal, CUDA, D, Delphi, Dylan, Fortran, Graphical G Programming Language,Halide, Haskell, Java bytecode, Julia, Kotlin, Lua, Objective-C, OpenGL Shading Language, Ruby, Rust, Scala, Swift, Xojo, and Zig. Sometimes I think mention of Rust in the title just gets upvotes without even reading the article, here.
- thcz 7y agoThe C# compiler uses LLVM? Is that for something like Xamarin or something? I was under the impression that it was self-contained. Anyone knows the details of this?
- bdd 7y agoAnyone can build a frontend. "Java bytecode" is in that list too. It doesn't mean defacto compilers of these languages rely on LLVM.
- jerven 7y agoIn this case it is Azul Zing, a very serious product. It's one of the four major VM Jit compilers (OpenJ9, C2, Graal are the others in my opinion) [1] https://www.azul.com/products/zing/ https://www.azul.com/products/zing/
- pjmlp 7y agoThere are also PTC, Aicas, Virtenio, Ricoh and Gemalto all targeted to embededded deployments, of which, PTC and Aicas are the most well known ones. Sadly Excelsior is no more. I imagine that regular JIT compilers making AOT/JIT caches available, is what killed them.
- wtfleming 7y ago
- univerio 7y agoI assume it's doing `(N-2)(N-3)/2 + 2N - 3` instead of `N(N-1)/2` due to overflow concerns? But couldn't `(N-2)(N-3)` also possibly overflow, just supporting a larger range of `N`?
- devit 7y agoIn this assembly code it cannot overflow because N is a 32-bit integer and the multiplication gives a 64-bit result, which is converted to 32-bit only after shifting. I can't figure out why it doesn't use the simpler formula (other than the optimizer being bad).
- shmerl 7y ago> he was very disappointed because Rust version was twice as fast than the C version which was hand-optimised by pulling off all the tricks he knew to make it perform well. Why disappointed? It just highlights the quality of Rust's approach.
- ncmncm 7y agoEvidently the tricks were what made it slow. This happens all too often: once the code changes enough that the optimizer doesn't recognize the pattern anymore, it throws up its hands, and you're on your own. Some people call this optimizer roulette. It's not just compilers, either. CPUs have their own peephole optimizers, and patterns they recognize, or don't, and it can easily make a 2x difference in your run time depending on if it cottons to what you're trying, or doesn't.
- pjmlp 7y agoWith CPUs it gets even worse, because that clever optimized Assembly code can stop being so in another CPU or after a firmware update. The days of Z80, 6502 and similar are long gone.
- ojosilva 7y agoI wonder if the const syntax introduced in ES6 will ever result in const folding, or any JIT optimizations for code running in JS runtimes. Apparently, from what I've read, const is being ignored as far as optimiztions go and is only used as a way to prevent the developer from ever reassigning certain variables.
- boomer_joe 7y agoThere is nothing impressive about this. https://godbolt.org/z/S2tDDh https://godbolt.org/z/S2tDDh