Live data from Hacker News

My tutorial and take on C++20 coroutines

scs.stanford.edu

91–100 of 144 posts

Re: My tutorial and take on C++20 coroutines

#91

As a developer, when you decide to use bleeding edge C++?? extensions keep in mind that when you do so you're making it much, much harder to run any of your code on a distro more than 4 years old. Is it worth it?

Similarly, as a user, keep in mind that when you use a 4 year old distro, it becomes much, much harder to run any software that has adopted new C++ features.

At least now. Back during the golden age of desktop from 2000 to about 2010 things were stable. But then dev funding for linux and it's primary libs went back to be about creating the most bleeding edge, fastest, possible environment for running server farms for mega-corps and desktop stability was abandoned.

The end result is the fever that is containerization as a symptom of the sickness that is future shock from devs always using the latest.

Re: My tutorial and take on C++20 coroutines

#92
I've used C++ coroutines (with io_uring). They are really useful in this case and allows one to write code like one would with the simple blocking API. And from what I've read they are better than Rusts coroutines for this use case (and for the yield use case they aren't good).

It adds an additional foot-gun w.r.t. to e.g. by-reference parameters to functions and their lifetime. The function parameter might not be alive anymore after a "co_await" and it doesn't require any capture (like lambda) or has any hint about this being the case.

Then, the tooling isn't there yet (other than the missing standard library). Gdb doesn't show correct lines and can't print local variables when in coroutines. If there is a deadlock one can't see the suspended coroutine (and its call stack) holding the lock, etc.. Back to printf debugging...

Re: My tutorial and take on C++20 coroutines

#93
post #92

I've used C++ coroutines (with io_uring). They are really useful in this case and allows one to write code like one would with the simple blocking API. And from what I've read they are better than Rusts coroutines for this use case (and for the yield use case they aren't good). It adds an additional foot-gun w.r.t. to e.g. by-reference parameters to functions and their lifetime. The function parameter might not be al…

> And from what I've read they are better than Rusts coroutines for this use case

Reference please? In what sense are they better, and what makes them better?

Re: My tutorial and take on C++20 coroutines

#94

Earlier quoted context omitted.

For me coming from Rust, this just seems way overly complicated to me. Rust's async doesn't even need a heap to work: https://lights0123.com/blog/2020/07/25/async-await-for-avr-w...

Requiring heap allocation for coroutines was extremely contentious to say the least. But because C++ does not have a borrow checker, all the other allocation-less designs proved to be very error prone. C++ coroutines do support allocators [1], so it is not a huge issue, but it does complicate the design further. [1] the compiler is also allowed to elide the allocation, but a) it is not clear how this is different fro…

Does that mean that every nested coroutine (async call) needs another heap allocation, or just the top level one?

Re: My tutorial and take on C++20 coroutines

#96
post #94

Earlier quoted context omitted.

Requiring heap allocation for coroutines was extremely contentious to say the least. But because C++ does not have a borrow checker, all the other allocation-less designs proved to be very error prone. C++ coroutines do support allocators [1], so it is not a huge issue, but it does complicate the design further. [1] the compiler is also allowed to elide the allocation, but a) it is not clear how this is different fro…

Does that mean that every nested coroutine (async call) needs another heap allocation, or just the top level one?

What do you mean nested call? As a first approximation, you need an heap allocation for each coroutine function instance for its activation frame. Every time a coroutine instance is suspended, the previously allocated frame is reused. If you instantiate a coroutine from another coroutine, then yes you need to heap allocate again, unless the compiler can somehow merge the activation frames or you have a dedicated allocator.

Re: My tutorial and take on C++20 coroutines

#97
post #93
post #92

I've used C++ coroutines (with io_uring). They are really useful in this case and allows one to write code like one would with the simple blocking API. And from what I've read they are better than Rusts coroutines for this use case (and for the yield use case they aren't good). It adds an additional foot-gun w.r.t. to e.g. by-reference parameters to functions and their lifetime. The function parameter might not be al…

> And from what I've read they are better than Rusts coroutines for this use case Reference please? In what sense are they better, and what makes them better?

Rust's Futures don't have asynchronous destructors (I don't know if coroutines do).

When a Future is aborted early, it's destroyed immediately with no remorse. This means it can't simply offer its own buffers to the kernel, because the kernel could write back after the Future has been freed.

An API contract that includes "just be careful not to do the stupid thing" is not good enough by Rust's standards, so the only way to guarantee safety would be to have Future's destructor wait synchronously until the I/O operation is cancelled on the kernel side, but that's inelegant in an async context.

Re: My tutorial and take on C++20 coroutines

#98
What I find interesting about coroutines is that they are relatively trivially achieved in hand-coded assembly using `jmp` and being careful about registers in a way that makes sense. `jmp`ing around is a pretty normal way to code in assembly. In a sophisticated environment they become a beast. It surprises me that we actually do lose something meaningful when abstracting to higher level languages.

Re: My tutorial and take on C++20 coroutines

#99

What I find interesting about coroutines is that they are relatively trivially achieved in hand-coded assembly using `jmp` and being careful about registers in a way that makes sense. `jmp`ing around is a pretty normal way to code in assembly. In a sophisticated environment they become a beast. It surprises me that we actually do lose something meaningful when abstracting to higher level languages.

Of course we lose something when we abstract to higher level languages, that's exactly what it means to abstract out. It'd be more surprising other way around. For example, when you're programming in a language like Haskell, Python or Java you cannot micromanage your pointers etc. This is because these languages abstract away the memory layout of objects. If you do hacky things and override the memory intentionally you can cause e.g. garbage collection to misbehave etc. On the other hand in C++ or C you can manage exactly how objects are encoded in the memory, when and how you allocate memory for your system. All these things are not things you're meant to be able to "customize" in higher level languages.

Re: My tutorial and take on C++20 coroutines

#100
post #83
post #19

Some notes: - C++20 coroutines are stackless , meaning that multiple coroutines will share a single OS thread stack. This is non-obvious when you first look in to them them because coroutines look just like functions. The compiler does all the work of ensuring your local variables are captured and allocated as part of the coroutine yield context, but these yield contexts are not stack frames . Every coroutine you inv…

It seems really dumb that they are stackless. If you are saving/restoring the stack pointer anyway in your yield routine it's trivial to set it to a block of memory you allocated in the initial coroutine creation. Is there no setjmp/longjmp happening? Are C++ 20 coroutines all just compiler slight-of-hand similar to duff's device with no real context switching?

> It seems really dumb that they are stackless

Why? C/C++ already has stackful coroutines. And that seems extremely wasteful unless you know you'll run the coroutines at the same time... with single threaded stackful coroutines, you'd get two stacks and only ever use one at a time. that wastes megabytes, requires heavier context switching, makes cache misses more likely, etc.

Post reply on HN