4 ms·
If the author is reading this, this might be the greatest single article I’ve read that expresses the benefits of Rust and how minimal the trade-offs are. Wonde
by bluejekyll 7y ago
If the author is reading this, this might be the greatest single article I’ve read that expresses the benefits of Rust and how minimal the trade-offs are. Wonderfully written piece, thank you!
BTW, in your bounds checking examples, I think it can be further simplified (would need to check the output to see if it's any better), this:
fn fill_1024(array: &mut [u8], color: u8) {
// Perform an explicit, checked, slice of the array before
// entering the loop.
let array = &mut array[..1024];
for i in 0..1024 {
array[i] = color;
}
}
Could be:
fn fill_1024(array: &mut [u8], color: u8) {
for i in &mut array[..1024] {
*i = color;
}
}
Here's the playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=832233ec0b3319fcec86c2330be93523 https://play.rust-lang.org/?version=stable&mode=debug&editio...
Thanks again, this is a wonderful piece.
- masklinn 7y agoI'm quite terrible at assembly, but it looks like both got inlined and compiled to a memset: leaq 8(%rsp), %rdi movl $1024, %edx movl $255, %esi callq *memset@GOTPCREL(%rip) for the 0xFF fill and leaq 8(%rsp), %rdi movl $1024, %edx movl $238, %esi callq *memset@GOTPCREL(%rip) for the 0xEE fill. However it turns out the original also compiles to that, because the inlining means the fixed size array is visible to the optimiser.
- jchw 7y agoWith lower opt-level, it does not get optimized to memset, and the succinct version actually compiles better, eliding the bounds checking in the loop as well as, for some reason, needing a smaller stack. https://godbolt.org/z/Wvuen5 https://godbolt.org/z/Wvuen5 This leads me to believe the shorter approach is actually better.
- masklinn 7y ago> With lower opt-level Yeah but who'd do that? OTOH I found an "issue" with the playground: the compiler realises that since only a single array is ever created the functions can only ever be called with the proper bounds, so even with inline(never) everything gets compiled down to a memset without bounds check. And trying to defeat that by modifying buffer to be 1023, the compiler realises the assertions are always going to crash so it creates a weird-ass "bad" version (which it calls), only implements the slicing (and its error) in the base and doesn't even bother outputting code for the simpler version. Tricksty compiler. Using rand() and slicing so it can't know whether it's getting slices of 1023 or 1024 elements and generating a random fillchar (otherwise the fillchar gets moved into the functions) finally gets it to generate the expected code… and remove one of the two functions, calling the other one instead. So yeah, at this point I will conclude your version works just as well as the article's at getting optimised.
- firethief 7y ago> > With lower opt-level > Yeah but who'd do that? Anyone who does `cargo run`. A project I'm working on right now is slow enough in debug builds that I usually debug it with light optimizations enabled. Lean on the optimizer and you'll find it a leaky abstraction.
- masklinn 7y ago> Anyone who does cargo run. The entire discussion starts from looking at getting bounds checks optimised away, not optimising seems counter-productive to this concern? > lean on the optimiser and you’ll find it a leaky abstraction. The abstraction here is not really leaky though, observable behaviour would be as expected t’es, it’s when you look at his this behaviour is achieved that things start looking odd.
- firethief 7y ago> The entire discussion starts from looking at getting bounds checks optimised away, not optimising seems counter-productive to this concern? I'm aware of the history of the conversation, but the point where I joined concerned which idiom performs better unoptimized, a case which is relevant because everybody cargo-runs. > The abstraction here is not really leaky though, observable behaviour would be as expected t’es, it’s when you look at his this behaviour is achieved that things start looking odd. In many circumstances performance is part of observable behavior.
- jchw 7y agoI think you have illustrated the point quite well actually: >And trying to defeat that by modifying buffer to be 1023, the compiler realises the assertions are always going to crash so it creates a weird-ass "bad" version (which it calls), only implements the slicing (and its error) in the base and doesn't even bother outputting code for the simpler version. With optimizations off, we can see what the compiler is doing before optimization passes potentially obfuscate it. In practice when the call is nested behind many layers it may not be able to elide the bounds check in the loop in all cases; the opt-level may be hiding the truth because it is defeating the test case. Whether that's true in this case or not is irrelevant, of course; I'd say it's almost always preferable to have better code as early as possible in the code generation process.