Earlier quoted context omitted.
You need "-C opt-level=3 -C target-cpu=native -C target-feature=+ssse3" I actually wrote about it last week: http://tmccrmck.github.io//post/rust-optimization-partii/
There's a little typo in your C implementation of popcount: you reference `x` when you probably meant `n`
Rust Performance Pitfalls
91–100 of 112 posts
Re: Rust Performance Pitfalls
#92> With some investment into optimizations, matching or exceeding C’s speed should be possible in most cases. What class of algorithms is faster in Rust than it is in C?
Theoretically, Rust's memory ownership/aliasing rules are stricter and more granular than C's restrict, so some pointer-heavy code could optimize better. Rust is very good at inlining by default. Rust makes it easy to use stack-allocated structures. But C can do the same, it's just a matter of effort.
Both languages are low level enough that you can use them as a portable assembly and endlessly tweak them to one-up the other. If some C code doesn't optimize well you can write a more contorted code that will.
Re: Rust Performance Pitfalls
#93> With some investment into optimizations, matching or exceeding C’s speed should be possible in most cases. What class of algorithms is faster in Rust than it is in C?
I don't think it can be generalized like that. Theoretically, Rust's memory ownership/aliasing rules are stricter and more granular than C's restrict, so some pointer-heavy code could optimize better. Rust is very good at inlining by default. Rust makes it easy to use stack-allocated structures. But C can do the same, it's just a matter of effort. Both languages are low level enough that you can use them as a portabl…
Maybe something that leverages signed overflow, overlong shifts, or type punning.
But if you have enough control over your codebase to mandate the compiler/flags its built with, then you can generally tell the major C compilers to act like Rust in these cases.
That said, the expected win for Rust over C(++) in practice is that you can be more "reckless", because you have a stronger type system protecting you from messing things up. A production-quality C(++) codebase might rightly do more copies, use more reference counting, and use less concurrency just because the risk of doing otherwise isn't worth the potential performance wins.
Organizations have limited resources to commit to optimizing/verifying code. Rust is intended to get you more bang for your buck.
Re: Rust Performance Pitfalls
#94I have been doing some exploration of how well Rust optimizes Iterators and have been quite impressed. Writing a iterator to provide the individual bits supplied by an iterator of bytes means you can count them with fn count_bits >(it : I) -> i32{ let mut a=0; for i in it { if i {a+=1}; } return a; } Counting bits in an array of bytes would need something like this let p:[u8;6] = [1,2,54,2,3,6]; let result = count_bi…
How much of this is the Rust compiler and how much is generic LLVM optimization passes?
However, the compiler does have perfect aliasing info, and it does provide a subset of this info to LLVM (I don't think LLVM IR currently supports more fine grained aliasing info being provided from the compiler). This does help certain optimizations; though I'm unsure if this one is one of them.
Re: Rust Performance Pitfalls
#95Earlier quoted context omitted.
We actually did have this once, but it wasn't really worth it, so it was removed. https://news.ycombinator.com/item?id=6940624 is the HN discussion, but it looks like the link might now be wrong? It was also a very, very long time ago, and so today's Rust might be different enough that those reasons don't apply any more.
The reason at that point vis that there weren't any practical benefits and that it was preferable to wait for a more general mechanism. The first claim is dubious, but I can empathize with second, as long as it doesn't become tacked on. Haskell can do cool optimizations that make it feel like magic sometimes.
Meanwhile mapping in Rust generally takes an iterator and produces another iterator that will apply the given closure to the current element as it's yielded. So the naive codegen for iter().map().map().map().collect() is exactly what list fusion is trying to produce -- no temporary lists.
TL;DR: making "map" having the monadic `T[U] -> T[V]` signature is really expensive. ¯\_(ツ)_/¯
Re: Rust Performance Pitfalls
#96Earlier quoted context omitted.
I don't think it can be generalized like that. Theoretically, Rust's memory ownership/aliasing rules are stricter and more granular than C's restrict, so some pointer-heavy code could optimize better. Rust is very good at inlining by default. Rust makes it easy to use stack-allocated structures. But C can do the same, it's just a matter of effort. Both languages are low level enough that you can use them as a portabl…
Yeah the only thing that Rust might have over C, in terms of really optimized implementations, is low-level idioms that C declares to be UB, but Rust declares to be defined (generally to be what x64 hardware does). Maybe something that leverages signed overflow, overlong shifts, or type punning. But if you have enough control over your codebase to mandate the compiler/flags its built with, then you can generally tell…
I'm glad to see somebody articulate this observation. SaferCPlusPlus[1] is meant to, in part, bring this benefit to existing C++ code bases. The question is, would a borrow checker for C++ make sense?
[1] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus
Re: Rust Performance Pitfalls
#97> for i in 0..(xs.len()) { let x = xs[i]; // do something with x } > should really be this: > for x in &xs { // do something with x } I am curious why the compiler can't rewrite the former to the latter?
(I may have gotten something wrong, this is my first Rust program! Yay!)
Re: Rust Performance Pitfalls
#98Earlier quoted context omitted.
To me "your code breaks in surprising ways" means something more like "the user sees gibberish/wrong results" and not "your program segfaults". Segfaulting is not actually a surprising result to me - I have seen it over and over on out of bounds access.
A buffer overflow doesn't necessarily cause a segfault - that's the problem! A guaranteed segfault would be a completely valid and safe way to handle a buffer overflow. But segfaults only happen when your program tries to access memory it does not own. It is improbable that an overflowing buffer is straight at a page boundary, doubly so if the buffer is allocated on the stack. Instead, you get gibberish output, an in…
All the solutions and problems in computing stem from the fact that we're programming completely deterministic Turing machines that do exactly what they're told.
Re: Rust Performance Pitfalls
#99Earlier quoted context omitted.
A buffer overflow doesn't necessarily cause a segfault - that's the problem! A guaranteed segfault would be a completely valid and safe way to handle a buffer overflow. But segfaults only happen when your program tries to access memory it does not own. It is improbable that an overflowing buffer is straight at a page boundary, doubly so if the buffer is allocated on the stack. Instead, you get gibberish output, an in…
Yes, the overflow either segfaults your program, or doesn't. It's not undefined. Computers don't perform random operations. Just say "it will produce buffer overflow" - that is absolutely a defined operation: the CPU will execute a load from an address that hasn't been defined at that place in code and will either contain leftover data, or refer to an unmapped page, triggering a segfault. This isn't quantum mechanics…
Just saying "or doesn't" is not a useful description. The vast majority of horrible broken behaviors fit inside of that bucket, where it doesn't segfault but then goes on to do the wrong thing at an unexpected place.
And because we're using an optimizing compiler, you can't give a simple description like "overflows a buffer". When the compiler assumes your code is correct, it might output instructions that behave in 'impossible' ways when fed invalid data. For example an if/else that takes neither branch, or both branches, or code that verifies a number has a certain value yet outputs a completely different value. You can only make assertions about what a particular compile will do, and that's obsolete information immediately.
Re: Rust Performance Pitfalls
#100Earlier quoted context omitted.
That's really impressive, but I'm a little surprised LLVM didn't optimize that inner loop to a popcnt instruction.
You may have to specify a flag like "-C target-cpu=native" -- not all CPUs support popcnt, so LLVM can't generate it without knowing details about the target. I can't get the compiler on godbolt.org to generate it either way, though.