Live data from Hacker News

Async-await on stable Rust

blog.rust-lang.org

331–340 of 392 posts

Re: Async-await on stable Rust

#331
post #72

Earlier quoted context omitted.

I also think async (the paradigm) is kind of weird in rust world. I agree with https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... .

Author's solution is threads: > But if you have threads (green- or OS-level), you don’t need to do that. You can just suspend the entire thread and hop straight back to the OS or event loop without having to return from all of those functions. Correct me if I'm wrong, but wasn't the lack of threads one of the biggest reasons why NodeJS originally outperformed most of its competitors? Spinning up threads for each conc…

I'm pretty sure that was the official talking point at the time, and some people may have even been motivated enough to actually belief it.

Of course that all changed once Node got threads.

Re: Async-await on stable Rust

#332
post #312

Earlier quoted context omitted.

The solution suggested by that article is to use M:N threading, which was tried in Rust and turned out to be slower than plain old 1:1 threading. If you don't want to deal with async functions, then you can use threads! That's what they're there for. On Linux they're quite fast. Async is for when you need more performance than what 1:1 or M:N threading can provide.

How hard was it tried? I imagine there's a reason that languages like Go adopted M:N threading... obviously part of the reason is that it's way more scaleable, but userspace threading is also supposed to be faster, as context switches don't need to switch to kernel space... were the problems tight loops (which AFAIK is also the problem in Go)? or maybe it's just so much easier / more efficient if you also have GC...

In my opinion they tried hard enough. Here are the links to the discussions of that time if you are into that kind of thing:

https://mail.mozilla.org/pipermail/rust-dev/2013-November/00...

https://mail.mozilla.org/pipermail/rust-dev/2013-November/00...

https://github.com/rust-lang/rfcs/blob/master/text/0230-remo...

It is clear that they really wanted to make green threading work, but in the end it proved incompatible with other goals of the language.

The main problem as I understand it is with the stack: you can't make the stack big from the beginning (or your threads wouldn't be lightweight anymore) so you need to grow it dynamically. If you grow it by adding new segments to the linked list, you get the "stack thrashing"/"hot split" problem: if you are unlucky and have to allocate/deallocate a segment in a tight loop the performance suffers horribly. Go solved the problem by switching to contiguous relocatable stacks: if there is not enough space in the stack, a bigger contiguous chunk of memory is allocated and old contents of the stack are copied there (a-la C++ vector). Now there is a problem with references to the stack-allocated variables - they become invalid. In go this problem is solvable because it is a managed garbage-collected language so they can simply rewrite all the references to point to the new locations but in rust it is infeasible.

Re: Async-await on stable Rust

#333

Earlier quoted context omitted.

await allows to write concurrent (for IO) or parallel (for CPU) code like it was serial. The issue it solves is programmer having trouble executing parallel code in their head, and when relationship became intricate (a computation graph) they just breakdown and write buggy software. A scheduler is targeted at use cases. A preemptive scheduler optimize for latency and fairness and would apply for real-time (say live a…

You don't need await for data (CPU) parallelism. You'd typically use something like https://github.com/rayon-rs/rayon or OpenMP instead.

OpenMP does not handle nested parallelism.

Compute-bound parallelism is not always data parallelism, for example a tree search algorithm would need to spawn/async on each tree nodes.

Re: Async-await on stable Rust

#334

Earlier quoted context omitted.

await allows to write concurrent (for IO) or parallel (for CPU) code like it was serial. The issue it solves is programmer having trouble executing parallel code in their head, and when relationship became intricate (a computation graph) they just breakdown and write buggy software. A scheduler is targeted at use cases. A preemptive scheduler optimize for latency and fairness and would apply for real-time (say live a…

You can't "avoid state". State is essential to any computation. The only question is, can you express some of the state-relations as serial code? If "relationships became intricate (a computation graph)", chances are, you shouldn't use serial code anymore, because that splits dependencies in a two-class society: those that are expressed using code and those that are expressed using dead data. It is usually preferable…

Haskell avoids state.

When I say state I meant "shared state" is the bane of multithreading. Amdahl's law show that if 95% of your code is parallel you limit your speedup to 20x even with thousands of cores, any shared state contribute to those 5%.

Re: Async-await on stable Rust

#335

Earlier quoted context omitted.

Rayon uses iterators. Iterators are the dual of first order functions. Iterators are superior because they are more general. (You can implement pmap using iterators. You cannot implement iterators using pmap.)

Does Rayon use iterators, or just iterator-like combinators? I don’t think a for-loop would work with Rayon, would it? You can also definitely implement iterators using first-order functions: const iterate = array => { const step = index => ({ value: array[index], next: () => step(index + 1) }); return step(0); } // add some combinators on top First order functions can in fact implement anything if you’re willing to…

Hm. You're right. I hadn't used Rayon in so long I forgot it is indeed iterator-like combinators using an entry point called `par_iter`.

Re: Async-await on stable Rust

#336
post #317

Earlier quoted context omitted.

Afaik Rust's futures compile to a state machine, which is basically just a struct that contains the current state flag and the variables that need to be kept across yield points. An executor owns a list of such structs/futures and executes them however it sees fit (single-threaded, multi-threaded, ...). So there is no stack per future. The number of stacks depends on how many threads the executor runs in parallel.

> the current state flag the variables that need to be kept across yield points. you mean like... a stack frame?

Kinda like a stack frame, but more compact and allocated in one go beforehand.

Re: Async-await on stable Rust

#337
post #323

Earlier quoted context omitted.

> Async I/O gives awesome performance No, it doesn't really. 'Async' is a strictly Python problem, due to the insanity of the GIL. Predictably, the Python solution to it is also insane. Why you have to turn a sane language like Rust into an insane one by cargo-culting a solution to a non-problem is a mystery to me. Oh well, good thing at least C++ hasn't dropped the ball.

You are mixing up a lot of stuff here. - Asynchronous programming is completely orthogonal to Python - If you don't need it, don't use it. Rust without asynchronous functions still looks and works like before - By the way: welcome to C++20's coroutines

> - Asynchronous programming is completely orthogonal to Python

Theoretically, yes. In practice, people are just copying Python mistakes word-for-word, in an attempt to score the "coolness" factor that async garnered in the Python community. (Never mind that the "coolness" came from solving a problem that doesn't even exist in other languages...)

If you want to see what asynchronous programming would look like if it weren't copied from Python, look at the recent C++ proposal.

> - If you don't need it, don't use it. Rust without asynchronous functions still looks and works like before

The problem is that every Python library now comes in two versions, regular and 'async'. Even if you don't need or care about anything 'async'.

I guess splintering your code base into two incompatible flavors is just the pythonic way of doing things? Now that the 2 vs 3 insanity is finally dying down, they needed to start a fresh one?

Why is Rust trying hard to repeat these mistakes?

> - By the way: welcome to C++20's coroutines

That I already answered above.

Re: Async-await on stable Rust

#338
post #328

Earlier quoted context omitted.

I don't understand what your objection is. It's a given that what I wrote applies to Rust. This is a thread about Rust. I didn't say that M:N threading is always slower than 1:1. Besides, fibers don't emit essentially the same code as async code. One has a stack, and the other doesn't. That's a significant difference.

It's isn't a given that M:N threading is slower than 1:1 threading even in Rust. A particular implementation you tried exhibited that behavior. > One has a stack, and the other doesn't. That's a significant difference. They both have some memory area to which they write state. Calling it "a stack" refers to the abstraction in the programmer's mind, not to how the memory is actually written/read. It is true that in or…

> It's isn't a given that M:N threading is slower than 1:1 threading even in Rust. A particular implementation you tried exhibited that behavior.

I don't see any way around the problems of segmented stacks and FFI. There is no way to implement stack growth by reallocating stacks and rewriting pointers in Rust, even in theory. It would break too much code: there is a lot of unsafe (and even safe!) code out there that assumes that stack pointer addresses are stable. In fact, async/await in Rust had to introduce a new explicit pinning concept in order to solve this exact problem while remaining backwards compatible. And when calling the FFI, you have to switch to a big stack, which was an insurmountable performance problem. Rust code by its nature is FFI-heavy; it's part of the niche that Rust finds itself in.

Re: Async-await on stable Rust

#339
post #327

Earlier quoted context omitted.

And that overhead is a deal-breaker in Rust.

Sure, but that overhead is also not essential, but a feature of Go's particular implementation. Fibers aren't one thing and there are many, many ways of implementing them. As I said before, implementing them for Rust well would have likely required changes to LLVM and Web Assembly, and even then it would be harder than async/await, perhaps to the point of being too hard to be worth it and probably against aspects of…

> Sure, but that overhead is also not essential, but a feature of Go's particular implementation.

The only way to get around the FFI performance problem would be for all fibers to have big stacks. At that point you've thrown away their biggest selling points: high scalability and fast spawning.

Post reply on HN