Live data from Hacker News

Rust Performance Pitfalls

llogiq.github.io

81–90 of 112 posts

Re: Rust Performance Pitfalls

#81
post #74
post #39

Earlier quoted context omitted.

"saying it "requires unsafe" does not imply it can blow up in your face" Good argument. If you advise someone to use unsafe the responsible thing to do is explain precisely what the consequences are besides "it's faster."

For any API that requires the `unsafe` keyword to use, improper usage of that API risks memory unsafety.

There's a subtlety here: the "actual" memory unsafety may manifest elsewhere, if the `unsafe` keyword just allows violating constraints that other (possibly-safe) functions rely on. That is, this touches on the whole "boundary of `unsafe` is the module", where functions marked `unsafe` might do perfectly safe things internally but break invariants that other pieces of code assume are true. From the view of "an API" == "an individual function", it is true that incorrect use of `unsafe` code may not risk any memory unsafety in isolation (e.g. one can pass any integer to Vec::set_len and nothing bad will happen in that call), but it is not so true in the more conventional broader view of an API.

Re: Rust Performance Pitfalls

#82

Earlier quoted context omitted.

Because in the former case, the optimizer has to prove that the length of the array cannot change during the body of the loop, while in the latter case, that's guaranteed by the language semantics.

Doesn't `let x = xs[i]` immutably borrow from xs? So for the duration of x's lifetime (which is the entire for-body block), xs cannot be changed and therefore its length must remain the same. Though this might be information that rustc knows about but not LLVM.

In the case of "let x = &xs[i]" it would immutably borrow. But we'd need more MIR optimizations to make use of that fact.

Re: Rust Performance Pitfalls

#83
post #59

I 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…

> If this produces simiarly nice code (I haven't checked yet) I'll be very happy You didn't specify what the type of `buffer` was, so I picked a `u8`. This code[1]: pub struct Thing { buffer: Vec , } impl Thing { pub fn copy(&mut self, data: &[u8]) { for (dest, &source) in self.buffer.iter_mut().zip(data) { *dest = source; } } } Produces this assembly: _ZN10playground5Thing4copy17hf523bcb10e2298f3E: .cfi_startproc pu…

>The call to `memcpy` is what makes me happy.

That's the sort of thing I was hoping to see.

>Your `count_bits` already exists as a combination of iterator adapters (`filter`[2] and `count`[3]):

That's the problem with simple examples. I don't actually want to count bits. It was just the minimum workload I could think of to generate a result from the conversion.

Seeing how the for (a,b) in ai.zip(bi) works well I'll probably be writing a bitmap glyph renderer that is basically if b {*a=color}

Re: Rust Performance Pitfalls

#84
post #79
post #67

Earlier quoted context omitted.

> If you're trying to say "there exists Rust code that contains `unsafe` blocks that is memory-safe", then this is obviously (hopefully!) correct, because 100% of the time we hope that our `unsafe` blocks are correctly implemented. Yes, and additionally there are some algorithms that are safe, but to implement require unsafe blocks. I think it's obvious that there are patterns of memory access that are safe, possibly…

Even if someone that you trust is telling you to use a specific unsafe API and that "it's no less safe than when we do the same in C/C++", proper responsible usage of that API always requires reading the documentation to determine which invariants must be upheld, if only because you ought to be documenting those exact same invariants (and describing the measures that you take to uphold them) in your own code. Don't t…

If someone says "here's a simple mergesort implementation. It requires unsafe in a few sports, for performance"[1] or even "you can implement a fairly normal mergesort, but oyu may want to use unsafe in areas A and B for speed", I view that differently than if someone says "you can access strings without confirming they are UTF-8 encoded with str::from_utf8_unchecked, but it requires unsafe".

One, has, for the most part a self contained implementation and is part of a very well known algorithm. Checking that the implementation doesn't have any obvious flaws may be sufficient.

The other has implications that far outlive the small suggested bit of code. Until you've correctly made sure that anything resulting from this call has been confirmed to conform to UTF-8, there is a risk in it's use in any number of string processing routines.

The article, to it's credit, does mention that problem with UTF-8 when working with it unchecked, albeit vaguely. What I don't think would have been sufficient for an article that aims to help people would be to say "it required unsafe" and leave it at that. That would be suggesting a routine for performance reasons without sufficiently addressing the real downsides.

Thus, my assertion, that simply noting that something requires unsafe is sufficient to denote in all cases the consequences of the suggestion, as I interpreted rabidferret comment to imply.

Another way of stating my argument is "when suggesting unsafe as a possible solution, it behooves you to mention any non-obvious consequences this specific suggestion might entail." That's a fairly uncontroversial view, in my eyes, so I'm not sure exactly why I've had to explain it four separate times now.

1: Like in the rust standard sort algorithm.

Re: Rust Performance Pitfalls

#85
post #59

I 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…

That's really impressive, but I'm a little surprised LLVM didn't optimize that inner loop to a popcnt instruction.

Re: Rust Performance Pitfalls

#86
post #59

I 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…

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.

Re: Rust Performance Pitfalls

#88
post #86

Earlier 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.

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/

Re: Rust Performance Pitfalls

#89
post #86

Earlier quoted context omitted.

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.

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`

Re: Rust Performance Pitfalls

#90
post #86

Earlier quoted context omitted.

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.

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/

You can drop `-C target-feature=+ssse3` since `target-cpu=native` will cover that. The `-C opt-level=3` isn't necessary either, but if you drop that, it looks like there's a function that doesn't get inlined. However, even without opt-level=3, the popcnt instruction is still emitted.
Post reply on HN