Live data from Hacker News

Wild performance tricks

davidlattimore.github.io

31–40 of 86 posts

Re: Wild performance tricks

#31

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.

The optimization not getting applied doesn't mean that "anything could happen". Your code would just run slower. The result of this computation would still be correct and would match what you would expect to happen. This is the opposite of undefined behaviour, where the result is literally undefined, and, in particular, can be garbage.

Re: Wild performance tricks

#32

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.

As an example, parts of the C++ standard library (none of the core language I believe though) are covered by complexity requirements but implementations can still vary widely, e.g. std::sort needs to be linearithmic but someone could still implement a very slow version without it being UB (even if it was quadratic or something it still wouldn't be UB but wouldn't be standards conforming).

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

#33

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

> straightforward, efficient architecture and bug-free code

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…

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

#35

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

> 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 transitively, yeah, that's going to be pretty bad.

Re: Wild performance tricks

#36

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

> Wouldn't a relational database help deal with this?

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

#37
post #3

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

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

#38
post #15
post #2

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

Memory safe doesn't imply memory efficient and reasonable. Wrapping everything in Arc is the opposite of simple & easy.

Re: Wild performance tricks

#39
post #37

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

I think sudo is a great example. It's not much more secure than just logging in at root. It doesn't really protect malicious attackers in practice. And it's more of an annoyance than it protects against accidental mistakes in practice.

Re: Wild performance tricks

#40

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

Have you written a linker before? It sounds like you’re describing I/O but that isn’t the work being done here by the linker.
Post reply on HN