Live data from Hacker News

A four year plan for async Rust

without.boats

191–200 of 236 posts

Re: A four year plan for async Rust

#191

Earlier quoted context omitted.

The point of async is to move concurrency from the OS into the process. The specific issue is the context switch. The compiler, with async is able to be much more performant than the context switch that the OS provides. Depending on the kind of application you're writing, this is either splitting hairs or very, very important. Applications that handle many (hundreds, 1000s,) of concurrent IO will have a noticeable pe…

Just a note that it seems many people think the main problem isn't the context switch. Rather, Linux by default allocates a very large stack to every thread, and thus many threads lead to high memory usage. Async makes this better. See e.g. this recent clip from one of the engineers on Project Loom in which they argue that the context switch is relatively low overhead: https://youtu.be/07V08SB1l8c?si=i0v9w90Kb_M0I4gP…

At least on Windows I've reconfigured the stack space. (I had to run an experiment that required a lot of threads.)

Is this something that's hard to do on Linux?

Re: A four year plan for async Rust

#192
post #182

Why yield is not a suffix operator like await?

`.await` being postfix makes it easy to chain with further method calls (eg `foo().await.bar().await`). `yield expr` returns `()`, so there is no use in chaining it.

Thanks for the explanation. I was thinking of Python's yield were it evaluates to what is .send() back in the generator.

Re: A four year plan for async Rust

#193

Earlier quoted context omitted.

Yes, this is a major difference. But at this point, let's be honest - why has most stuff converged on Tokio? IMO it's not for reasons wholly of merit - it additionally won a popularity contest as one of the first movers. Why the popularity contest? Because people want an async executor, but they just want it to work . These people largely don't care about the benefits between different executors. I think they'd like…

I actually think we have a cultural problem due to the fact that many of the people coming into Rust recently are coming from a "full-stack" webdev background, where "frameworks" are huge, and feels like nobody worries about being coupled to one framework or another, just picking the right one. Which is in large part due to the history of JavaScript and so on. This is an influx of people who sense that tokio is the "…

I agree with you 100%, but members of the Rust project have stated in the past that async, in not insignificant ways, is a play to deliberately target the higher level web/service based crowd. So, the cultural issue is partially self-inflicted.

I'm not even sure what I want in Rust. All I know is that people have a legitimate gripe being sold on Rust async for high level work, having it ergonomically fall much flatter than it should. It's very hard to satisfy everyone, which they are learning day by day.

I do wish for the async story to mature a bit more so the implementors have a chance to show the community how good it can be. But the project needs to consider its messaging to the users it has courted over.

Re: A four year plan for async Rust

#194

Earlier quoted context omitted.

I don’t really get your point. You cannot, (should not), do blocking IO in async in any language. The language provides libraries to do non blocking IO. What is unclear about that? Multi threading has nothing to do with async, except as a way to run certain things in an executor thread to avoid blocking. Also I don’t really know what you mean by async IO…. even in the languages you reference I would guess IO operatio…

> You cannot, (should not), do blocking IO in async in any language. On platforms with 1:1 scheduling, of course you can. Blocking I/O executed on another thread, with a callback to execute when done, becomes async I/O (from the user's PoV). Ofc, when we talk about async I/O, we also refer to the kernel APIs being used, such as select/poll/epoll/io_uring. Say, working with Epoll is usually done via a single-threaded…

That's...not...how threads or async work...?

> Blocking I/O executed on another thread, with a callback to execute when done, becomes async I/O (from the user's PoV).

That's not what we're talking about when we discuss languages with async I/O, though. That's just bog-standard synchronous I/O with multithreading.

> The read/write operations are still potentially blocking, so for efficiency you need multiple threads.

That doesn't actually follow. The entire point of language-level async I/O is to be able to continue doing other work while waiting for the kernel to finish an I/O operation, without spawning a new OS thread just for this purpose.

Re: A four year plan for async Rust

#195
post #56

I was a huge opponent of Rust, but finally decided to give it a try in anger once again. I began writing a large application, and noticed that many of my libraries only offered async versions, and the promise was quite appealing -- not having to worry about threads or concurrency as long as I followed certain rules. What I ended up with was an incredibly slow application because of the limitations of Rust async, and…

In these debates I find myself very confused. I feel very comfortable with async ergonomics in Rust. It seems like the argument against it is that it has poor ergonomic... but how? It's rather straightforward to go from sync to async and async to sync you just have to code the strategy in. If you're in sync and need to run async, you need to run it in some executor (either in threads or in single-thread concurrency o…

> It seems like the argument against it is that it has poor ergonomic... but how?

It depends on what you're comparing it against. If you are comparing Rust async against other languages, then the major difference is Rusts borrow checker. In every implementation of async, you effectively move whatever state is needed off the stack and into a separately allocated memory area. In Rust that separate area is a closure.

This interacts badly with Rusts borrow checker. The borrow checker needs to know the lifetime of any object you deal with. It has two "base truths", by which I mean life times it already knows about that you can derive other life times from: static and the stack. If they don't suffice you have to handle life time management yourself and at run time using Rc or Arc or something. Being forced to do that complicates your types and slows the code down. The root cause is async in Rust removes one of those two base truths: the stack. So now you are forced to write that ugly manual life time management code far more often.

This is unique to Rust. Every other language I know of that implements async has garbage collection, so while it remains true they also move stuff off the stack it doesn't change anything. You still use the same types, and apart from sprinkling async's and await's here and there and indenting your closures, your code remains the same.

This is also why I think green threads are a much better fit for Rust than async. Under the hood green threads and async are very similar: they are both ways of doing event driven I/O. Their performance characteristics are near identical. The main difference is while async forces you to move your state to a different area, green threads you do it as before and store it on the stack, just like normal code. In fact green thread code looks identical to normal code. The only change you have to make to convert some code to green threads is change the name of the I/O calls to use non-blocking versions (which is something you also have to do with async, of course). But since the stack is still available all those fights with the borrow checker async creates go away, as does all the extra syntax async requires.

It doesn't come for free of course, so the run time performance of green threads and async is not absolutely identical. In async every task shares the one stack, whereas in green threads they each get a new one. This creates some extra memory overhead, chews up considerable address space (which normally isn't backed by memory) in order to protect against stack overflow, and it costs a bit more to set up a stack. But once a task is setup green threads are going to be bit faster you aren't moving stuff and and off the stack and you don't get hit with those additional run time life time checks you were forced to introduce for async. Mitigating green threads overheads somewhat, a process that is handling 1000's of concurrently connections is unlikely to be running one a machine that is memory constrained, so the extra memory probably doesn't matter. (In reality a Raspberry Pi with 4GB of memory can handle 1000's of stacks.) And it's likely to be a 64 bit machine where address space is nearly free, so the "considerable extra address" space also doesn't matter.

Still, I can think of once place it does matter. You can have two styles of generators: ones take the async approach and ones that take the green thread approach (ie, allocate an extra stack to each generator). Rust nightly does have generators and they currently take the green thread approach(!). But, generators tend to be short lived. You tend to use them to iterate over an array, rather than serving a web request (the typical use a task is put to). That means the overhead of creating the stack for a green thread isn't a small proportion of the time a long running task takes, but could well dominate the time it takes to iterate over a small array. Thus async is a much better fit for iterators.

Currently Rust has this arse about: it has async for long running tasks, and green threads for generators in nightly. With this announcement it looks like this will be 1/2 fixed. Great!

Re: A four year plan for async Rust

#196

Earlier quoted context omitted.

The point of async is to move concurrency from the OS into the process. The specific issue is the context switch. The compiler, with async is able to be much more performant than the context switch that the OS provides. Depending on the kind of application you're writing, this is either splitting hairs or very, very important. Applications that handle many (hundreds, 1000s,) of concurrent IO will have a noticeable pe…

Just a note that it seems many people think the main problem isn't the context switch. Rather, Linux by default allocates a very large stack to every thread, and thus many threads lead to high memory usage. Async makes this better. See e.g. this recent clip from one of the engineers on Project Loom in which they argue that the context switch is relatively low overhead: https://youtu.be/07V08SB1l8c?si=i0v9w90Kb_M0I4gP…

The stack memory won’t actually be physically allocated upfront - like all user space memory it is virtual.

Re: A four year plan for async Rust

#197

Earlier quoted context omitted.

Just a note that it seems many people think the main problem isn't the context switch. Rather, Linux by default allocates a very large stack to every thread, and thus many threads lead to high memory usage. Async makes this better. See e.g. this recent clip from one of the engineers on Project Loom in which they argue that the context switch is relatively low overhead: https://youtu.be/07V08SB1l8c?si=i0v9w90Kb_M0I4gP…

At least on Windows I've reconfigured the stack space. (I had to run an experiment that required a lot of threads.) Is this something that's hard to do on Linux?

It's the same, but this issue gets to one of the hearts here:

Both of these APIs are set by you, the user. You can choose how big to set your stack size, it's true. However, what value do you actually set? Too high, and you're still using too much memory, though admittedly less. Too low, and you either need to accept death by stack overflow, or runtime detect this case and fix things up.

With async/await in Rust, the compiler can statically see how large the stack size needs to be. Each "thread" will have a perfectly sized stack. No user intervention required, no fiddling with settings.

Re: A four year plan for async Rust

#198
post #56

I was a huge opponent of Rust, but finally decided to give it a try in anger once again. I began writing a large application, and noticed that many of my libraries only offered async versions, and the promise was quite appealing -- not having to worry about threads or concurrency as long as I followed certain rules. What I ended up with was an incredibly slow application because of the limitations of Rust async, and…

Rust Async has very real constraints but it's definitely _not_ supposed to make your app slow. Most will agree that it is often best avoided unless demonstrated to be required by performance requirements. IMO this advice also applies to other lang's async, but even moreso in Rust. The complexity is real.

Re: A four year plan for async Rust

#199

Earlier quoted context omitted.

In these debates I find myself very confused. I feel very comfortable with async ergonomics in Rust. It seems like the argument against it is that it has poor ergonomic... but how? It's rather straightforward to go from sync to async and async to sync you just have to code the strategy in. If you're in sync and need to run async, you need to run it in some executor (either in threads or in single-thread concurrency o…

> It seems like the argument against it is that it has poor ergonomic... but how? It depends on what you're comparing it against. If you are comparing Rust async against other languages, then the major difference is Rusts borrow checker. In every implementation of async, you effectively move whatever state is needed off the stack and into a separately allocated memory area. In Rust that separate area is a closure. Th…

Without commenting on the rest of this post:

> Every other language I know of that implements async has garbage collection,

Small note: C++ also has it these days, and does not have GC. You are (as far as I know) left to your own with object lifetimes, as with non-async/await.

Re: A four year plan for async Rust

#200

Earlier quoted context omitted.

I actually think we have a cultural problem due to the fact that many of the people coming into Rust recently are coming from a "full-stack" webdev background, where "frameworks" are huge, and feels like nobody worries about being coupled to one framework or another, just picking the right one. Which is in large part due to the history of JavaScript and so on. This is an influx of people who sense that tokio is the "…

I agree with you 100%, but members of the Rust project have stated in the past that async, in not insignificant ways, is a play to deliberately target the higher level web/service based crowd. So, the cultural issue is partially self-inflicted. I'm not even sure what I want in Rust. All I know is that people have a legitimate gripe being sold on Rust async for high level work, having it ergonomically fall much flatte…

> the higher level web/service based crowd.

Not everyone that writes network services is "higher level" or "web".

Post reply on HN