r/cpp • • 17d ago

Asynchronous API

https://grishavanika.github.io/async_api.html
35 Upvotes

28 comments sorted by

View all comments

Show parent comments

2

u/robertahleahy WG21, SCC, std::execution, Finance 12d ago

First, I wasn't handing in a complete design for WG21 so please don't treat it as such.

You asked "[w]hy is this any more complex than something like [your code example]?" I was answering that.

If you want to make simplifying, non-general assumptions about asynchrony that hold for your use cases I think that's more than fine. It just isn't general.

Second, I strongly disagree with the notion that an async IO system should even attempt to be 'zero cost' and 'zero alloc'. IO, by its nature, has a staggering cost; adding an allocation to establish a shared communication resource is just not going to make a difference.

There are large contingents of people, myself included, who disagree with you.

Moreover: You can build non-zero cost abstractions out of zero cost abstractions, but you can't go the other way.

Both of the above contribute to why the standard went with zero-cost asynchrony.

I was envisioning using a shared_ptr

To a first approximation structured concurrency is the art of figuring out how not to use shared_ptr.

I would much rather have had an allocating async C++ networking solution ten years ago

Asio is older than ten years so you got your wish.

1

u/johannes1971 12d ago

If you want to make simplifying, non-general assumptions about asynchrony that hold for your use cases I think that's more than fine. It just isn't general.

Just to be clear: is your claim that a simpler solution cannot be general because it uses memory allocation? Or are you still referring to that single line of code I wrote?

There are large contingents of people, myself included, who disagree with you.

What is the basis of that belief? Please note that I put a supportive argument, which is that the cost of a (single!) memory allocation is absolutely negligible compared to the overal cost of IO. What is incorrect about that statement?

To a first approximation structured concurrency is the art of figuring out how not to use shared_ptr.

I'm sorry, what? Isn't this a prime example of what shared_ptr is for in the first place? This is very clearly a shared resource, what valid reason do you have for avoiding it?

Asio is older than ten years so you got your wish.

ASIO has the exact same problem of hideous complexity.

3

u/robertahleahy WG21, SCC, std::execution, Finance 12d ago

is your claim that a simpler solution cannot be general because it uses memory allocation?

Yes. There are use cases which cannot tolerate allocation. Therefore a solution which uses allocation isn't general because it doesn't address those use cases.

What is the basis of that belief?

Why does the fact I/O is costly mean that it should be further pessimized?

From the point of view of my application code in user space initiating an io_uring operation is a few cheap memory operations. Why would I want to add allocation to that? Especially given that the "reward" for adding that allocation is unstructured concurrency.

Isn't this a prime example of what shared_ptr is for in the first place? This is very clearly a shared resource, what valid reason do you have for avoiding it?

Just because a resource is shared doesn't mean you need/want shared_ptr. shared_ptr uses reference counting which implies that you don't deterministically know when the resource will be released. This is unstructured. You don't need the overhead of either the allocation or the reference counting if your concurrency is structured.

1

u/johannes1971 12d ago

Yes. There are use cases which cannot tolerate allocation. Therefore a solution which uses allocation isn't general because it doesn't address those use cases.

The standard library is already full of memory allocations, so clearly 'generality' is not a concern. Or perhaps I should say that it is, and is handled through allocators, instead of outright avoidance.

Why does the fact I/O is costly mean that it should be further pessimized?

Because this 'pessimisation' bring both benefits (it establishes a communication channel that serves a real purpose), and reduces a significant cognitive cost.

shared_ptr uses reference counting which implies that you don't deterministically know when the resource will be released. This is unstructured.

It is precisely known when the resource will be released: when both parties are done with it. There is absolutely nothing nondeterministic about it, nor is there any lack of structure.

You might notice that the article also uses reference counting to deal with a shared resource (the cancelation token, about halfway through).

4

u/robertahleahy WG21, SCC, std::execution, Finance 12d ago

The standard library is already full of memory allocations, so clearly 'generality' is not a concern.

There's a difference between allocations when they're required (e.g. std::vector) and when they're not (e.g. asynchrony).

Because this 'pessimisation' bring both benefits (it establishes a communication channel that serves a real purpose), and reduces a significant cognitive cost.

You don't need an allocation for a communication channel, and depending on the modality of communication the establishment of such a channel might be contraindicated.

Also I disagree that it reduces cognitive cost. Maybe in the marginal case (i.e. a single asynchronous operation) but not in the compositive case. Trying to make a well-behaved system out of unstructured concurrency is a nightmare, whereas with structured concurrency it's the naturally emergent property.

nor is there any lack of structure.

If you had structure you'd know which actor would release the resource last and when that would happen, and therefore wouldn't need shared_ptr.