Live data from Hacker News

Wild performance tricks

davidlattimore.github.io

51–60 of 86 posts

Re: Wild performance tricks

#51

Earlier quoted context omitted.

Rust's number types have functions like "wrapping_add" or "overflowing_add", which do not panic when overflowing and instead explicitly wrap around or return a result that must be checked. You can easily write code that does not contain any possible panic points, if you want.

I don't think it's quite as easy to guarantee panic freedom as you think. For example: do logging frameworks guarantee no-panic behavior? People can add logging statements practically anywhere, especially in a large team that maintains a codebase over significant time. One innocuous-looking debug log added to a section of code that's temporarily violated invariants can end up putting the whole program into a state, p…

> A safe section of code that panics unexpectedly might preserve memory safety invariants but hork the higher-level logical invariants of your application

The usual way of dealing with this is to use impl Drop to cleanup properly. Resources are guaranteed to be dropped as expected on panic unwinds. Eg the database transaction rolls back if dropped without committing.

> Imagine an attacker who can force a panic in a network service, aborting his request but not killing the server, such that the panic on his next request grants him some kind of access he shouldn't have had due to the panic leaving the program in a bad state.

You need to be more specific. Why would the web server be left in a bad state because of such panics (in safe rust). All the memory will be cleaned up, all the database transactions will be cleaned up, mutexes might get poisoned, but that's considered a bug and it'll just cause another panic the next time someone tries to lock the mutex.

Re: Wild performance tricks

#52
post #41

Earlier quoted context omitted.

Unsafe isn’t a security feature per se. I think this is where a lot of the misunderstanding comes from. It’s a speed bump that makes you pause to think, and tells reviewers to look extra closely. It also gives you a clear boundary to reason about: it must be impossible for safe callers to trigger UB in your unsafe code.

That's my point; I think after a while you instinctly repeat a command with sudo tacked on (see XKCD), and I wonder if I'm any safer from myself like that? I'm doubtful that those boundaries that you mention really work so great. I imagine that in practice you can easily trigger faulty behaviours in unsafe code from within safe code. Practical type systems are barely powerful enough to let you inject a proof of valid…

What you postulate simply doesn’t match the actual experience of programming Rust

Re: Wild performance tricks

#53

Earlier quoted context omitted.

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

The idea that a language can handle any complexity for you is an illusion. A language can automate a lot of the boring and repetitive small scale work. And it can have some of the things you would have otherwise coded yourself as built-ins. However you still have to deal with the complexity caused by buying into these built-ins. The larger a project gets the more likely the built-ins are to get in the way, and the mo…

> The idea that a language can handle any complexity for you is an illusion

I think this is wrong on its face. We wouldn't see any correlation between the language used and the highest complexity programs achieved it in.

As recently mentioned on HN it takes huge amounts of assembly to achieve anything at all, and to say that C doesn't handle any of the complexity you have to deal with when writing assembly to achieve the same result is absurd.

EDIT: > Now go research how some of the most complex, flexible, and efficient pieces of software are written.

I'm quite aware. To say that the choice of say, C++ in the LLVM or Chromium codebase doesn't help deal with the complexities they operate over, and that C would do just as well at their scale... well, I don't think history bears that out.

Re: Wild performance tricks

#54

Earlier quoted context omitted.

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

I understand why Alexander Stepanov thought the complexity requirements were a good idea, but I am not convinced that in practice this delivers value. Worse, I don't see much sign C++ programmers care.

You mentioned particularly the C++ unstable sort std::sort. Famously although C++ 11 finally guarantees O(n log n) worst case complexity the libc++ stdlib didn't conform. They'd shipped worst case O(n squared) instead.

The bug report saying essentially "Hey, your sort is defective", was opened in 2014. By Orson Peters. It took until 2021 to fix it.

Re: Wild performance tricks

#55

Earlier quoted context omitted.

> However you do have to be careful in critical code because things like integer overflow can also raise a panic. This is incorrect. Only in debug builds does it raise a panic. In release Rust has to make the performance tradeoff that C++ does and defines signed integer math to wrap 2’s complement. Only in debug will signed overflow panic. Unsigned math never panics - it’s always going to overflow 2’s complement.

> Only in debug builds does it raise a panic. Correctness in debug builds is important, isn't it? That said, panic on integer overflow in debug builds is unfortunate behavior. Overflow should cause an abort, not a panic. > make the performance tradeoff that C++ does and defines signed integer math to wrap 2’s complement In C++, signed overflow is undefined behavior, not wraparound. This property is useful to the opti…

What's the rationale behind aborting and not panicking in debug? Unwinding and flushing buffers seems like a better default with debug binaries.

Re: Wild performance tricks

#56

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…

> However you do have to be careful in critical code because things like integer overflow can also raise a panic. This is incorrect. Only in debug builds does it raise a panic. In release Rust has to make the performance tradeoff that C++ does and defines signed integer math to wrap 2’s complement. Only in debug will signed overflow panic. Unsigned math never panics - it’s always going to overflow 2’s complement.

You can enable overflow panics in release build, so if you're a library, you have to play it safe because you don't know how people will build your library.

Re: Wild performance tricks

#57

Earlier quoted context omitted.

> in something like C or C++ you could do these things via simple pointer casts No you don't. You explicitly start a new object lifetime at the address, either of the same type or a different type. There are standard mechanisms for this. Developers that can't be bothered to do things correctly is why languages like Rust exist.

And that is safer... how?

   Foo foo{}; init(*(Bar *)foo);
is UB in most cases (alignment aside, if Bar is not unsigned char, char, std::byte or a base class of Foo). This is obvious why, Foo and Bar may have constructors and destructors. You should use construct_at if you mean to;

For implicit-lifetimes types (iirc types with trivial default constructors (or are aggregates) plus trivial destructors), you can use memcpy, bit_cast and soon std::start_lifetime_as (to get a pointer) when it is implemented.

If I'm not mistaken, in C, the lifetime rules are more or less equivalent to implicitly using C++'s start_lifetime_as

Re: Wild performance tricks

#58

Earlier quoted context omitted.

> However you do have to be careful in critical code because things like integer overflow can also raise a panic. This is incorrect. Only in debug builds does it raise a panic. In release Rust has to make the performance tradeoff that C++ does and defines signed integer math to wrap 2’s complement. Only in debug will signed overflow panic. Unsigned math never panics - it’s always going to overflow 2’s complement.

> Only in debug builds does it raise a panic. Correctness in debug builds is important, isn't it? That said, panic on integer overflow in debug builds is unfortunate behavior. Overflow should cause an abort, not a panic. > make the performance tradeoff that C++ does and defines signed integer math to wrap 2’s complement In C++, signed overflow is undefined behavior, not wraparound. This property is useful to the opti…

You can choose whether panics immediately abort, and you can also choose whether integer overflow panics in releas builds.

Personally I would often choose both, overflow panics and also panics abort, so if we overflow we blow up immediately.

Re: Wild performance tricks

#59
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".

Yup -- yet another article only solving language level problems instead of teaching something about real constraints (i.e. hardware performance characteristics). Booooring. This kind of article is why I still haven't mustered the energy to get up to date with Rust. I'm still writing C (or C-in-C++) and having fun, most of the time feeling like I'm solving actual technical problems.

This was an article distilled from a talk at a Rust developers conference. Onbviously it’s going to make most sense to rust devs, and will seem unnecessary to non-Rust devs.

Re: Wild performance tricks

#60

Earlier quoted context omitted.

The idea that a language can handle any complexity for you is an illusion. A language can automate a lot of the boring and repetitive small scale work. And it can have some of the things you would have otherwise coded yourself as built-ins. However you still have to deal with the complexity caused by buying into these built-ins. The larger a project gets the more likely the built-ins are to get in the way, and the mo…

> The idea that a language can handle any complexity for you is an illusion I think this is wrong on its face. We wouldn't see any correlation between the language used and the highest complexity programs achieved it in. As recently mentioned on HN it takes huge amounts of assembly to achieve anything at all, and to say that C doesn't handle any of the complexity you have to deal with when writing assembly to achieve…

No, C doesn't actually handle the _complexity_ of writing assembly. It abstracts and automates a lot of the repetitive work of doing register allocation etc -- sure. But these are very local issues -- I think it's fair to say that the complexity of a C program isn't really much lower than the equivalent program hand-coded in assembler.

I'm not sure that LLVM would be the first consideration for complex, flexible, efficient? It's quite certainly not fast, in particular linking isn't. I'm not sure about Chromium, it would be interesting to look at some of the more interesting components like V8, rendering engine, OS interfacing, the multimedia stack... and how they're actually written. I'd suspect the code isn't slinging shared_ptr's and unique_ptrs and lambdas and is keeping use of templates minimal.

I would have thought of the Linux kernel first and foremost. It's a truly massive architecture, built by a huge number of developers in a distributed fashion, with many intricate and highly optimized parts, impressive concurrency, scaling from very small machines to the biggest machines on the planet.

Post reply on HN