7 ms·
> ...giving incremental compilation speed-ups of up to 13% across many benchmarks. It also added a lot more comments to explain what is going on in that code, a
by ChrisSD 7y ago
> ...giving incremental compilation speed-ups of up to 13% across many benchmarks. It also added a lot more comments to explain what is going on in that code, and removed multiple uses of unsafe.
I wanted to emphasize that last point because there's sometimes a misconception that using `unsafe` always means speeding up the code. In truth you can have speed and safety. Where unsafe is needed is when it's necessary to break from Rust's memory model or to otherwise write code that Rust cannot reason about.
I mention this because I've noticed even people familiar with Rust who sometimes imply `safe == slow` and `unsafe == fast`. At the extreme end you get some new users who think merely adding an `unsafe` block around code will provide a speed boost. It will not. In fact `unsafe` doesn't do anything itself. It simply allows calling some functions and performing a few other operations that would otherwise be forbidden. Sometimes this is indeed necessary but often it isn't.
- petr_tik 7y agoIt's great you emphasise it and I would generalise it even further. Any perf optimisation should be considered after reliably measuring (controlling for hot/cold cache, overall system load among others) its effect, instead of blindly adding unsafe everywhere. Similar to people who sprinkle their Cpp code with inline thinking it will force the compiler to inline. In fact, sometimes you can improve perf by adding an additional safe operation to your code! http://troubles.md/rust-optimization/#assert-conditions-beforehand http://troubles.md/rust-optimization/#assert-conditions-befo... The key takeaway from all perf optimisation articles to me is the methodology used for realiably verifying the effect of suggested changes. It's awesome to see the author include failed experiments.
- dirtydroog 7y agoIn modern C++, inline is used to prevent 'multiple-definitons' errors, not to actually inline the function.
- rmdashrfstar 7y agoThat’s terrifying. I will choose to avoid C++ projects if given the chance.
- coldpie 7y agoIt is shocking how often people suggest dubious code changes to "improve performance" with exactly zero benchmarks, or solely artificial benchmarks. Computers and compilers are far more complicated than your naive code analysis implies, you _must_ prove any performance changes with robust, real-world benchmarks, especially if your changes make the code more unsafe or harder to understand.
- hyperman1 7y agoWhen I was younger, I implemented the 8 queens problem on my 386, and micro optimized the hell out of it. Assembler, cycle counting, disable interrupts, etc... Didn't switch to protected mode, but it came close. Then I found a smarter algorithm, and my qbasic proof of concept was magnitudes faster. Ouch! This kid learned something about the nature of optimization that day. Now Im an old fart but even older farts than me stumble into this.
- acdha 7y agoThat’s a great lesson to have early in your career, too. My favorite example of that was a memory-bound app where a key function had been implemented in both C and x86 assembly. Someone tested and found that the assembler version had been slight performance loss since (IIRC) after the Pentium Pro, and since nobody had wanted to substantially change the assembly code going C-only meant they actually refactored the data access patterns and saw a substantial improvement.
- petr_tik 7y agoNot to excuse people, who provide no metrics or benchmarks for their "performance optimisations", I want to acknowledge that benchmarking is hard. I hope something like NixOS will enable people to write and publish "reproducible" (in quotes because precise numbers are impossible) benchmarks by enabling you to setup your OS, user space and compilation flags exactly like in the benchmark.
- OJFord 7y agoOh that would be really compelling. I only tried NixOS briefly a while ago, but I think I recall you can switch (actually I think literally `switch`) environment like that pretty easily and back again? So your benchmark environment could actually be pretty minimal, or if you were sharing a bug reproduction it could be devoid of unrelated packages/config in your usual environment.
- bjoli 7y agoOn a similar note: some people seem to think immutable is slower than mutable. I have seen this many times, especially in my language of choice (scheme). A lot of people coming from python or c write loops using (while ...) and mutation, which in most schemes (and in many dynamic languages) is a nice way to get boxing penalties. Instead, a recursive function is almost always preferred. I have since since long preferred recursion. Which (not strictly relevant) leads me to my biggest gripe (which is pretty small) with rust. I like rust. I really do. But a couple of times a week I have to use loop when tail recursion would have been a lot clearer. In debug builds , rust disables sibling recursion (a less general form of tail recursion) which means I have to either use a trampoline macro (simple and slow) or some kind of syntax rewriting macros (complex and fast). Neither seem like a good idea. Should I just accept the ugliness of loop+mutation or is there a nice alternative?
- jfkebwjsbx 7y agoRecursive functions should be avoided whenever reasonably possible, for several reasons (performance, safety, optimizer shenanigans...). Immutable may be slower or faster than mutable, depending on what one is writing and optimizer.
- bjoli 7y agoTail recursion is not slower. A properly tail recursive function can be transformed into a jump, and not mutating loop variables makes flow analysis less complex. Scheme implementations usually doesn't even bother doing type inference once you start using set!, and just implicitly boxes everything that you mutate. Of course you _can_ optimize it, but once you have a language with multi-shot continuations you are probably better off encouraging immutability.
- jfkebwjsbx 7y agoTail recursion is a trivial subset of recursion. If you are in that case, you are better off writing a loop and avoiding to rely on the optimizer. As for Scheme, that is a detail of Scheme, not general mutability vs. immutability.
- jfkebwjsbx 7y agoUnsafe does not imply fast, but safe does restrict what you can do. In other words, the optimal safe solution can be slower than the optimal unsafe one.
- wtetzner 6y agoOn the other hand, it's possible that safe code gives the compiler more guarantees, so it has more room to optimize. Whether your code is safe or unsafe can have a performance impact, but what the impact is depends on the specific situation.
- lidHanteyk 7y agoSo why is `unsafe` even in the language, then? It would seem to me that it simultaneously undermines safety guarantees, and it can't even ensure performance improvements.
- conradludgate 7y agoUnsafe can't ensure performance improvements, but you can sometimes get performance boosts using it. It should be a last resort if you have a bottle neck that is a result of memory restrictions. It's also how FFI works. If you're calling a c library, you cannot guarantee memory safety anymore since its outside of rusts domain, so it needs to be unsafe.
- majewsky 7y ago> Unsafe can't ensure performance improvements, but you can sometimes get performance boosts using it. Maybe to back this up with a concrete example, let's say I'm parsing something and I have a &[u8] (a string of bytes) which I have already verified contains only ASCII digits. I wanted to get the numerical value of that number. I don't want to write my own number parsing code, but std only has a parsing function for &str (UTF-8 strings), not for &[u8]. I could do let string = str::from_utf8(bytes); let value: u64 = string.parse()?; But then str::from_utf8 would iterate over the bytestring again to verify that it's valid UTF-8. This check is useless because I already know the string only contains ASCII digits. So in this case, I can improve performance with an unsafe block: //SAFETY: `bytes` was already proven to only contain ASCII digits let string = unsafe { str::from_utf8_unchecked(bytes) }; let value: u64 = string.parse()?; The performance gain comes not from unsafe per se, it comes from using a different function that skips the UTF-8 check. Since this function does not guarantee on its own that str's invariants are upheld, it's marked unsafe.
- dirtydroog 7y agoIsn't this a bit fragile though? It's totally fine as long as you own and understand the code. But what's to stop someone new and/or inexperienced from coming along and violating the 'already proven' claim? How will you know? Isn't there a risk of Rust code having "Don't ever change this" comments?
- k__ 7y agoI heard that C/C++ game engine devs would throw everything on the stack and do their own thing in terms of memory management. How does this approach play with Rust?
- est31 7y agoYou can do this in Rust as well, and it's in fact being done in the compiler: https://github.com/rust-lang/rust/search?p=2&q=TypedArena&unscoped_q=TypedArena https://github.com/rust-lang/rust/search?p=2&q=TypedArena&un... https://docs.rs/rustc-ap-arena/655.0.0/rustc_ap_arena/struct.TypedArena.html https://docs.rs/rustc-ap-arena/655.0.0/rustc_ap_arena/struct... In general, there is bad support in the standard for custom allocators, but there are crates that provide generic custom containers.
- jononor 7y agoNo problem at all with using the stack a lot? But it would be explicit in most cases. Much less chances of memory corruption issues than in C/C++ thanks to lifetime tracking. And much less copying due to default move semantics, and the assurances given by lifetime tracking.
- m12k 7y agoNot a Rust dev, but from what I understand, the lifetime tracking of Rust means you can safely give out references to stack allocated memory without worrying about corruption/segfaults/use-after-free errors or the like, because the compiler will catch it if that is possible, whereas in C++ you'd usually want to do defensive copying in situations like that. So in some cases, Rust actually allows you to fearlessly do things the most efficient way in a way you'd not be able to do in C++.
- MegaDeKay 7y agoReading the article made me wonder when the remove of unsafe was due to refactoring the code and when it was enabled by enhancements to the language since the code was originally written.
- remcob 7y agoAdding unsafe can even slowdown your code. The unsafe block prevents the compiler from making certain assumptions, which can prevent some optimizations from happening. 'unsafe' is like telling the compiler 'trust me on this', and the compiler (not fully understanding what is going on in there) wisely decides not mess with what you wrote.
- steveklabnik 7y agoAdding an unsafe block changes nothing about compiler assumptions. Some unsafe constructs do not have the same optimization potential as their safe counterparts.