6 ms·
I tend to agree. While asnyc/await (and callbacks and promises before) solves an efficiency problem via compiler magic and language extensions, it's not obvious
by Matthias247 4y ago
I tend to agree. While asnyc/await (and callbacks and promises before) solves an efficiency problem via compiler magic and language extensions, it's not obvious whether it's necessarily the best solution to the problem. The approach that Lunatic seems to take is building another runtime in userspace which takes care of scheduling while allowing methods to continue to look synchronous. A stackful coroutine framework would probably have allowed for the same without requiring WASM, so I'm kind of wondering whether that was considered too.
But ideally we would just use kernel threads, and maybe look into fixing performance for the use-cases where they don't work that well at the moment (which are not a lot actually!). It's so much simpler to understand for most programmers, tools (like debuggers, profilers, etc) just work, and suffers much less from interoperability issues.
- astrange 4y agoI don't think you should add something complicated like async/await purely for efficiency reasons, but it's good for expressivity too. Coroutines are good; I think you'd end up inventing coroutines and CPS (aka calling a callback at the end of your function instead of returning) just because they're useful, but once you have them, they're unsafe and unstructured i.e. you can forget to call them when you meant to. Async/await does more than just concurrency; it explodes your function into a rather complicated state machine, but at the same time gives you structured coroutine control flow that prevents some kinds of bugs. That's why Swift added it before concurrency: https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md https://github.com/apple/swift-evolution/blob/main/proposals... Some languages have async/await sugar built around futures instead; this is a mistake and it's less efficient too.
- Zababa 4y ago> Some languages have async/await sugar built around futures instead; this is a mistake and it's less efficient too. Could you elaborate on that?
- astrange 4y ago- The syntax isn't as good. (this is more like a lot of minor annoyances than one big point, but you can see it discussed in the Swift pitches) - It's not visible to compiler optimizations so it can't optimize coroutines. - It's not visible to the OS scheduler, or a green thread scheduler. Any pattern where you start an async task and then later wait on it leads to priority inversions, because until your more important thread is waiting on it, the scheduler doesn't know it's going to, so it can't inherit the priority.
- Zababa 4y agoThank you for the explanation.
- RcouF1uZ4gsC 4y agoI agree with what you are saying. Coroutines are a very valuable language feature. One example is generators. However, I think it is unfortunate that it is tied so much to concurrency. Even the names async/await suggest concurrency.
- wahern 4y ago> once you have them, they're unsafe and unstructured i.e. you can forget to call them when you meant to. At least in the case of asymmetric coroutines, such as what Lua supports, this is absolutely not the case. They're very structured--the caller invokes the callee, which always yields back to the caller. It's almost the same as simply calling another function. Yes, objects can persist beyond the invocation lifetime, but their lifetime is scoped to the lifetime of the coroutine object itself, which the caller manifestly holds. Async Rust is effectively built on asymmetric coroutines, AFAIU. The difficulties and differences lie in stack management--specifically, where and how that occurs, and how those details leak. > Async/await does more than just concurrency; it explodes your function into a rather complicated state machine All compilers explode functions into state machines. The question is how and where that state is managed. Non-async Rust, like many compiled languages, heavily relies on the underlying environment (hardware, operating system, ABI, etc) to set and restore stack and instruction pointers. A language like Lua uses virtual stacks, which are VM data structures that operate independently from the underlying "C stack". Async Rust sits somewhere in the middle. Async Rust is fundamentally a practical compromise and has little to do with safety, per se. If anything, managing lifetime guarantees might have been easier if Rust was able to push stack management down further in the runtime model, separating concerns more fully. Doing so would have complicated FFI interoperability, however, so it was a non-starter. Much the same is true for Swift. But if you completely control the runtime environment, such as through a VM or gating FFI access through a bridge, then it's difficult to not choose coroutines.
- Matthias247 4y agoMy comment was about comparing async/await as an evolution of other "async models" compared to just using native threads - which is mostly about efficiency. It's not so much about comparing an async callback based solution to asycn/await - which is as far as I understand what your comment is about. I agree that directly standardizing async/await or coroutines instead of a lower-level abstraction (callbacks, promises, etc) can improve both efficiency and ease of use.
- sushisource 4y ago> A stackful coroutine framework would probably have allowed for the same without requiring WASM Interestingly Rust actually had this built in originally, but it was taken out of the language since it could be added back in as a library, and the devs thought it was important that Rust did not hide any costs from the user. Which I can certainly buy. Especially once you consider use cases like embedded development. That said, it'd also suuuuure be nice to be able to opt-in to something like that and avoid the async headache.
- Matthias247 4y agoI fully agree with the decision to remove the mandatory runtime mandate - which allows Rust to run everywhere. The quoted sentence was more about the solution that Lunatic took to enable "synchronously looking code" - they apparently opted for WASM as an additional runtime layer to accomdate user-space scheduling. But it might have been possible to achieve similar goals by using stateful coroutines/fibers (similar to boost::coroutine, boost::context, etc). I guess there might be a win in using WASM due to them being able to insert cooperative yielding instructions into the generated code. But the tradeoff is that WASM raw execution speed is lower than native.
- sealeck 4y agoAsync/await doesn't increase efficiency, it increases saturation.
- Matthias247 4y agoIt increases efficiency, in the same sense as prior solutions for the same problem increased efficiency. E.g. if you write a webserver which intends to handle LOTS of active connections (5-7 digits of them), you might end up being able to handle N connections with a classic OS thread per request model (as e.g. original Apache used it). Going to an fully async model - as used e.g. in Nginx - will allow to make it 2xN connections for the same hardware, in exchange for having to deal with a lot more complexity during implementation. Which means you can either serve 2x as much traffic, or pay 50% of hardware and energy cost for the same amount => This is the increase in efficiency. Goig from a "low level async" (callbacks, reactor/proactor/etc) model to async/await intends to mostly keep the increase in efficiency, while making the programming model a bit more bearable and less error-prone.
- ekidd 4y ago> While asnyc/await (and callbacks and promises before) solves an efficiency problem via compiler magic and language extensions, it's not obvious whether it's necessarily the best solution to the problem. The nice thing about Rust is that if kernel threads solve your problem, you can just use kernel threads. Some common reasons for using async, however, include: 1. Handling a larger number of connections than the kernel can support using threads. 2. Writing co-routines that operate over streams. This is sort of the in-process version of Unix CLI tools communicating by pipelines. For various reasons, it often seems to be much better handled in async frameworks. 3. Using networking libraries that happen to be async. This is actually fairly common in Rust, and it's arguable not a good reason to use async. On the other hand, Rust has tools for wrapping async code in a sync API, and several of the most popular async libraries do this. Rust, however, does force you to choose between kernel threads and async coroutines. Rust does not support transparent green threads. This was an explicit design decision, largely driven by two goals: - Rust lives extremely "close to the machine". - Rust prefers to make the costs of operations explicitly visible. Both of these concerns pushed heavily towards removing Rust's original green thread runtime support. However, these two concerns are very specific to languages like Rust that focus on making expensive things visible. Other languages which are more forgiving of runtime overhead would probably be better served by supporting transparent green threads.
- kaba0 4y agoSure, rust is a low-level language, it needs absolute control over execution so it is very much validated in having an async keyword.