Hacker News new | ask | show | jobs
by hutao 99 days ago
One of the unwritten takeaways of this post is that async/await is a leaky abstraction. It's supposed to allow you to write non-blocking I/O as if it were blocking I/O, and make asynchronous code resemble synchronous code. However, the cost model is different because async/await compiles down to a state machine instead of a simple call and return. The programmer needs to understand this implementation detail instead of pretending that async functions work the same way as sync functions. According to Joel Sposky, all non-trivial abstractions are leaky, and async/await is no different. [0]

The article mixes together two distinct points in a rather muddled way. The first is a standard "premature optimization is the root of all evil" message, reminding us to profile the code before optimizing. The second is a reminder that async functions compile down to a state machine, so the optimization reasoning for sync functions don't apply.

[0] https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...

3 comments

One non-trivial problem with async in Rust is that it leads to code that allocates on one CPU and free memory on another. That kills a lot of optimizations that system allocators try to do with CPU local caching and harms performance badly especially on fat servers with a lot of CPUs. When one hits this problem, there is no easy solution.

Ideally using an allocator per request would solve this issue, but Rust has no real support for it.

A workaround that works is to stop using async and just use a native thread per request. But then most crates and frameworks these days use async. So indeed async abstraction us very leaky regarding the cost.

> The programmer needs to understand this implementation detail instead of pretending that async functions work the same way as sync functions.

Async is telling the OS "I'll do it myself" to threading and context switches.

I agree that from the perspective of the implementation of async code, it is in many ways an application doing its own threading and context switching. However, your Parent comment is written from the perspective of the dev writing and reasoning about the code. In that case, from the devs perspective, async is there to make concurrent code ‘look like’ (since it certainly is not actually) sequential code.

I think this type of confusion (or more likely people talking past one another in most cases) is a fairly common problem in discussing programming languages and specific implementations of concepts in a language. In this case the perceived purpose of an abstraction based on a particular “view point”, leads to awkward discussions about those abstractions, their usefulness, and their semantics. I don’t know if there is way to fix these sorts of things (even when someone is just reading a comment thread), but maybe pointing it out can serve to highlight when it happens.

Yeah the author makes a really poor example with the async case here.

Async in rust is done via cooperative scheduling. If you call await you enter a potential suspension point. You're willingly telling the scheduler you're done running and giving another task a chance to run. Compound that with something like tokio's work stealing and now you'll possibly have your task migrated to run on a different thread.

If this is in hot path making another call to await is probably the worst thing you can do lol.

The author demonstrates later with a dead simple inlining example that the asm is equivalent. Wonder why he didn't try that with await ;)