Earlier quoted context omitted.
It is true that it won't tell you, for sure. It's just that UB means something very specific when discussing language semantics.
I see. These optimisations might not be UB as understood in compiler lingo, but it is a kind of "undefined behaviour", as in anything could happen. And honestly the problems it might cause don't look that different from those caused by UB (from compiler lingo). Not to mention, using unsafe for writing optimised code will generate same-ish code in both debug and release mode, so DX will be better too.
Wild performance tricks
31–40 of 86 posts
Re: Wild performance tricks
#32Earlier quoted context omitted.
It is true that it won't tell you, for sure. It's just that UB means something very specific when discussing language semantics.
I see. These optimisations might not be UB as understood in compiler lingo, but it is a kind of "undefined behaviour", as in anything could happen. And honestly the problems it might cause don't look that different from those caused by UB (from compiler lingo). Not to mention, using unsafe for writing optimised code will generate same-ish code in both debug and release mode, so DX will be better too.
UB is really about the observable behavior of the abstract machine which is limited to the reads/writes to volatile data and I/O library calls [1]
[1] http://open-std.org/jtc1/sc22/open/n2356/intro.html
Edit: to clarify the example
Re: Wild performance tricks
#33Earlier quoted context omitted.
This is an issue that you would face in any language with strong typing. It only rears its head in Rust because Rust tries to give you both low-level control and strong types. For example, in something like Go (which has a weaker type system than Rust), you wouldn't think twice about, paying for the re-allocation in buffer-reuse example. Of course, in something like C or C++ you could do these things via simple point…
In C I wouldn't use such a fluffy high-level approach in the first place. I wouldn't use contiguous unbounded vec-slices. And no, I wouldn't attempt trickery with overwriting input buffers. That's a bad inflexible approach that will bite at the next refactor. Instead, I would first make sure there's a way to cheaply allocate fixed size buffers (like 4 K buffers or whatever) and stream into those. Memory should be use…
The grace with which C handles projects of high complexity disagrees.
You get a simple implementation only by ignoring edge cases or improvements that increase complexity.
Re: Wild performance tricks
#34> Even if it were stable, it only works with slices of primitive types, so we’d have to lose our newtypes (SymbolId etc). That's weird. I'd expect it to work with _any_ type, primitive or not, newtype or not, with a sufficiently simple memory layout, the rough equivalent of what C++ calls a "standard-layout type" or (formerly) a "POD". I don't like magical stdlibs and I don't like user types being less powerful than…
> Great example of Rust being built such that you have to deal with error returns and think about C++-style exception safety. Not really. Panics are supposed to be used in super exceptional situations, where the only course of action is to abort the whole unit of work you're doing and throw away all the resources. However you do have to be careful in critical code because things like integer overflow can also raise a…
So you can basically panic anywhere. I understand people have looked at no-panic markers (like C++ noexcept) but the proposals haven't gone anywhere. Consequently, you need to maintain the basic exception safety guarantee [1] at all times. In safe Rust, the compiler enforces this level of safety in most cases on its own, but there are situations in which you can temporarily violate program invariants and panic before being able to restore them. (A classic example is debiting from one bank account before crediting to another. If you panic in the middle, the money is lost.)
If you want that bank code to be robust against panics, you need to use something like https://docs.rs/scopeguard/latest/scopeguard/
In unsafe Rust, you basically have the same burden of exception safety that C++ creates, except your job as an unsafe Rust programmer is harder than a C++ programmer's because Rust doesn't have a noexcept. Without noexcept, it's hard to reason about which calls can panic and which can't, so it's hard to make bulletproof cleanup paths.
Most Rust programmers don't think much about panics, so I assume most Rust programs are full of latent bugs of this sort. That's why I usually recommend panic=abort.
[1] https://en.wikipedia.org/wiki/Exception_safety#Classificatio...
Re: Wild performance tricks
#35Earlier quoted context omitted.
> Great example of Rust being built such that you have to deal with error returns and think about C++-style exception safety. Not really. Panics are supposed to be used in super exceptional situations, where the only course of action is to abort the whole unit of work you're doing and throw away all the resources. However you do have to be careful in critical code because things like integer overflow can also raise a…
> be careful in critical code because things like integer overflow can also raise a panic So you can basically panic anywhere. I understand people have looked at no-panic markers (like C++ noexcept) but the proposals haven't gone anywhere. Consequently, you need to maintain the basic exception safety guarantee [1] at all times. In safe Rust, the compiler enforces this level of safety in most cases on its own, but the…
Wouldn't a relational database help deal with this?
> Without noexcept, it's hard to reason about which calls can panic and which can't, so it's hard to make bulletproof cleanup paths.
Unsafe blocks usually contain very low level code, so you can understand what your code does very accurately. If the unsafe code calls a dependency which calls 150 other dependencies transitively, yeah, that's going to be pretty bad.
Re: Wild performance tricks
#36Earlier quoted context omitted.
> be careful in critical code because things like integer overflow can also raise a panic So you can basically panic anywhere. I understand people have looked at no-panic markers (like C++ noexcept) but the proposals haven't gone anywhere. Consequently, you need to maintain the basic exception safety guarantee [1] at all times. In safe Rust, the compiler enforces this level of safety in most cases on its own, but the…
> If you panic in the middle, the money is lost. Wouldn't a relational database help deal with this? > Without noexcept, it's hard to reason about which calls can panic and which can't, so it's hard to make bulletproof cleanup paths. Unsafe blocks usually contain very low level code, so you can understand what your code does very accurately. If the unsafe code calls a dependency which calls 150 other dependencies tra…
Sure. It's just a toy example. There are lots of real programs in which you temporarily violate invariants in the course of performing some task, then restore them after. Another example that comes to mind is search tree rotations.
> Unsafe blocks usually contain very low level code, so you can understand what your code does very accurately.
Perhaps at first, but code evolves.
Re: Wild performance tricks
#37Earlier quoted context omitted.
You don't have to play this game - you can always write within unsafe { ... } like in plain old C or C++. But people do choose to play this game because it helps them to write code that is also correct, where "correct" has an old-school meaning of "actually doing what it is supposed to do and not doing what it's not supposed to".
That just makes it seem like there's no point in using this language in the first place.
The point is to escalate capability only when you need it, and you think carefully about it when you do. This prevents accidental mistakes having catastrophic outcomes everywhere else.
Re: Wild performance tricks
#38Every one of these "performance tricks" is describing how to convince rust's borrow checker that you're allowed to do a thing. It's more like "performance permission slips".
...Except that Rust is thread-safe, so expressing your algorithm in terms that the borrow checker accepts makes safe parallelism possible, as shown in the example using Rayon to trivially parallelize an operation. This is the whole point of Rust, and to say that C and C++ fail at thread-safety would be the understatement of the century.
Re: Wild performance tricks
#39Earlier quoted context omitted.
That just makes it seem like there's no point in using this language in the first place.
This is like saying there’s no point having unprivileged users if you’re going to install sudo anyway. The point is to escalate capability only when you need it, and you think carefully about it when you do. This prevents accidental mistakes having catastrophic outcomes everywhere else.
Re: Wild performance tricks
#40Earlier quoted context omitted.
This is an issue that you would face in any language with strong typing. It only rears its head in Rust because Rust tries to give you both low-level control and strong types. For example, in something like Go (which has a weaker type system than Rust), you wouldn't think twice about, paying for the re-allocation in buffer-reuse example. Of course, in something like C or C++ you could do these things via simple point…
In C I wouldn't use such a fluffy high-level approach in the first place. I wouldn't use contiguous unbounded vec-slices. And no, I wouldn't attempt trickery with overwriting input buffers. That's a bad inflexible approach that will bite at the next refactor. Instead, I would first make sure there's a way to cheaply allocate fixed size buffers (like 4 K buffers or whatever) and stream into those. Memory should be use…