Live data from Hacker News

Inside Rust's Async Transform

blag.nemo157.com

31–40 of 84 posts

Re: Inside Rust's Async Transform

#31

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

> And say there is absolutely no IO

Well, then we can simply optimize away your entire program as it does nothing and running it has no side effects.

Even if your program is entirely CPU bound, there are uses for writing in an async/await style. As an example, parsers can be quite natural to write in that style.

Or you can use it to have multiple computations running at the same time, and give updates on their progress. It is voluntary time slicing, which is significantly less overhead than the OS doing time slicing for you.

Re: Inside Rust's Async Transform

#32
post #25

Off topic, but I’d just like to point out how blindingly fast this site loads: it loads quite literally instantly for me (I’m on mobile so I can’t give precise figures) but I don’t think I’ve ever used a site that loads that fast ever before. Is the website author here? What are you running server side that’s giving such great performance?

Only two requests: favicon and html page itself. No front-end framework, no tracking. The only JS is the 10 lines necessary for the buttons in the upper right corner.

Gosh I wish more websites were this minimal. It's refreshing to use something so streamlined.

Re: Inside Rust's Async Transform

#33

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

I think the JS world might be particularly excited about async/await because other people just escape to threads when they don't want to block the whole thing on IO.

Both in JS or Rust, you don't gain anything just by declaring your thing to be async, or awaitable. Your function needs to be built around some kind of "primitive" that explicitly supports the "do something else in the meantime" mechanism. Using "await" on that thing lets your function piggyback on its support, but all your explicit, normal code is synchronously blocking as usual.

In Rust, I think it's a fairly established pattern to turn blocking code, where the blocking part is not some IO action that has explicit support for the futures mechanism, into a asynchronous, awaitable function by punting the work to a threadpool. That makes sense for CPU-bound work as well as IO done by libraries that don't support futures or things like disk IO where the OS might not actually have decent support for doing it in a non-blocking fashion.

I'm not sure if it's the canonical mechanism, but this crate seems to implement what I'm thinking of: https://docs.rs/futures-cpupool/0.1.8/futures_cpupool/

Re: Inside Rust's Async Transform

#34

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

CPUs do the physical calculation and the number of cores determines the number of calculations that can happen at the same time.

Software threads, from the OS to your application, run on one of these cores for a certain amount of scheduled time, then get switched out with some other thread. If threads are waiting on IO then they're not making much use of the time they get and are wasting CPU capacity.

An older approach is to just make more threads and switch them out faster, but this is very inefficient. Await/async is a way to let threads not get stalled by a single function and switch to a different function in that process that does have work available. It's basically another step of granularity in slicing CPU time within a thread.

The keywords do not force anything, they are just signals to the underlying software that it may pause and come back later if necessary, along with setting up state to track results. Some methods may still run all on the same thread if there's nothing else to do, or if the async result is already available and there is no waiting needed.

Most async/await is usually built on top of yield, generators, promises or other constructs that basically are state-machines or iterators.

Re: Inside Rust's Async Transform

#35

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

I think the JS world might be particularly excited about async/await because other people just escape to threads when they don't want to block the whole thing on IO. Both in JS or Rust, you don't gain anything just by declaring your thing to be async, or awaitable. Your function needs to be built around some kind of "primitive" that explicitly supports the "do something else in the meantime" mechanism. Using "await"…

The latest tokio has a work-stealing threadpools, so you don’t need to explicitly do this anymore, IIRC.

Re: Inside Rust's Async Transform

#36

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

> Is there any benefit to making this [compute_pi_digits()] function awaitable?

If you introduce suspension points in that (e.g. every 100 computed digits), then you can co-schedule other tasks (e.g. a similar `compute_phi_digits`) or handle graceful cancellation (e.g. if a deadline is exceeded, or its parent task aborted in the meanwhile).

Re: Inside Rust's Async Transform

#37

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

It's not about IO, it's about any long-running operation that can happen in a background thread. JS developers just think it is about IO because that's the only thing (web workers notwithstanding) in JS-land that can run in another thread.

Re: Inside Rust's Async Transform

#38

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

Incremental computation can be useful for better responsiveness even if you only have one thread. A simple example in JavaScript would be a Mandelbrot viewer, where you don't want to lock up the UI doing a heavy computation. So, you could have something that looks like an async call that really does the heavy computation in small chunks using idle callbacks, and the future completes when it'd done. (Using a background worker thread is probably better though.)

The async call itself doesn't return intermediate results, though, so you'd have to handle that a different way. And if you want to cancel the task, you need another way to handle that too.

Something like computing the digits of pi would be better represented by a stream or iterator since the caller should decide when it's done.

Re: Inside Rust's Async Transform

#39

Earlier quoted context omitted.

One difference that may exist is that in Rust, async fns don’t immediately execute, they simply create one of these values. I forget if JS and C# do something different, that is, the execute up until the first suspend point. This was one of the major design decisions we’ve made that’s different than other languages.

Just for comparison, Dart started out with async functions that suspended immediately, but in Dart 2, they switched to running synchronously to first await for performance (fewer unnecessary suspends) and to avoid race conditions. Without this, you sometimes had to write a write a wrapper function that does some synchronous setup and returns a Future, which was a bit annoying for stylistic reasons. There's an interes…

Immediately executing in Rust doesn't work very well for a few reasons. One of them is "pinning". In order to "start" a future, it must be pinned to a certain memory location and must never be moved from there anymore. If a future would start directly from the call which returns it, it wouldn't be possible to move the Future anymore. E.g. return it from another function, store it in a Vec, etc.

I found out it works also better together with some other features, like "select!" and the current cancellation mechanics. But I can't remember all the details right away, and it might be pretty hard to explain.

That said the choice makes sense for Rust for those reasons, but is not a general one! I think for Javascript, C# and Dart synchronously executing until the first await point is easier to understand. I e.g. felt that "async" in F# was a bit harder than in C# due to the non immediately executing property.

Re: Inside Rust's Async Transform

#40
post #15
post #3

Doesn't both JS (via Babel) and C# implement asynchronous functions as state machines in a similar fashion?

Yes, Babel's transform of `async` to ES5 is similar. It turns has to turn a function into a state machine. Rust goes a step further and turns the entire chain of futures into one large combined state machine. But the rest of the event-loop machinery is quite different. JS's async is still fundamentally callback-based. Rust's futures are polled. In JS there's a single global event loop and promises run automagically.…

Thank you for the detailed explanation!

Would you mind elaborating on the polled aspect of rust futures, or link me to some documation? Do you mean that there is a loop polling the result of a future? How does that work with things like select?

Post reply on HN