5 ms·
unsafe is sometimes necessary because Rust's safe APIs are needlessly inefficient. For example, consider a function for 16 byte equality: fn equals(v1: [u
by ridiculous_fish 6y ago
unsafe is sometimes necessary because Rust's safe APIs are needlessly inefficient.
For example, consider a function for 16 byte equality:
fn equals(v1: [u8; 16], v2: [u8; 16]) -> bool {
v1 == v2
}
rustc emits a branch to test if v1 and v2 point to the same thing, which is unnecessary (it can never be true!) and can ruin loop vectorization, etc. The useless branch is not serving any safety purpose but requires unsafe to avoid.
https://rust.godbolt.org/z/6973jG https://rust.godbolt.org/z/6973jG
The efficient version is quite a bit more verbose :(
- RomeoDelta 6y agoI know this is only responding to your one example, and perhaps your statement is still true generally, but I think that in this particular case it is not. In this godbolt [0] I tried using the safe version of equals with two values (from stdin so it can't just optimize the computation away, though I don't think it would) and it seems that when the compiler inlines the equals function, the inefficiencies are removed (which can be seen in the example::main section of the output; lines 212-216 look nearly identical, if not identical, to equals_unsafe in your example). Please let me know if I'm missing something or if there are any other safe APIs to be wary of. [0] https://rust.godbolt.org/z/W8dqaK https://rust.godbolt.org/z/W8dqaK
- ridiculous_fish 6y agoRight, the branch may or may not be optimized out; probably it depends on llvm's alias analysis. That's not good, it means you might break it accidentally. IME Rust is more dependent than C++ on these fragile optimizations. I think it is because idiomatic Rust is more abstract, and also because C++ has more advanced specialization features which allows implementing optimizations directly. For example in C++ you can say "if the type is uint8_t then do this instead..." but this is harder in Rust. You asked for other examples: consider a simple operation over an array, like bitwise not. A C-style for-loop does the obvious thing but idiomatic Rust emits a gazillion one-byte 'not' instructions. This doesn't involve 'unsafe' but it does illustrate that you really do have to babysit Rust to get efficient codegen. https://rust.godbolt.org/z/PjW5vo https://rust.godbolt.org/z/PjW5vo (In fairness C++ has its own stupid pitfalls too: https://travisdowns.github.io/blog/2019/11/19/toupper.html https://travisdowns.github.io/blog/2019/11/19/toupper.html)
- RomeoDelta 6y ago> the branch may or may not be optimized out That's true, and while I agree that it is a concern, is there ever a way to gain both ergonomics and optimization? In other words, aren't we always leaving it up to the compiler one way or another? You mention C++'s specialization, and I suppose that is a solution, but then you are (potentially, I can't say for certain) sacrificing compile times. Although I will admit that this is probably a fair trade for certainty of optimization, and it would be nice if Rust at least offered some amount of specializing. > you really do have to babysit Rust to get efficient codegen. https://rust.godbolt.org/z/PjW5vo https://rust.godbolt.org/z/PjW5vo Oh wow, that is interesting. Yeah, that probably speaks to Rust still being a relatively new language, although I would have expected LLVM to do something about that... In any case, that's certainly no zero-cost abstraction. Thanks for the neat info!
- archi42 6y ago> you really do have to babysit Rust to get efficient codegen Reminds me of the time we had to write a high performance SQL engine at university. In Java, and it boiled down to exactly the same realization: We spent a lot of time babysitting the GC instead of actually making the actual SQL engine better. The plan was to use C++ instead, but that option was dropped by the lecturer shortly before the project started :/
- aw1621107 6y agoI'm curious; is the extra cmp something that's actually required by Rust's semantics, or is this more along the lines of an optimizer bug/missed optimization?
- pthariensflame 6y agoThe latter, definitely.
- ridiculous_fish 6y agoThis particular case is because array delegates to slice for Eq, and slice has the pointer comparison - though it looks like there's some churn here with 'guaranteed_eq' so it may be fixed soon [1]. 1: https://doc.rust-lang.org/src/core/slice/mod.rs.html#6610 https://doc.rust-lang.org/src/core/slice/mod.rs.html#6610
- aw1621107 6y agoAh, ok. Thanks for explaining!