10 ms·
> So wondering if Rust provides any ability to add that kind of an N:M threading approach. Perhaps via an extension, macro or some other mechanism. I don't wan
by pcwalton 10y ago
> So wondering if Rust provides any ability to add that kind of an N:M threading approach. Perhaps via an extension, macro or some other mechanism.
I don't want M:N threading as Go implements it. It's a big loss of performance for marginal benefit over futures. In particular the libmill approach was tried in Rust and the results were far worse than 1:1.
However, assuming this takes off I would like to see async/await style syntactic sugar over futures down the road to make it easier to write code that looks blocking but actually isn't. Crucially, this sugar would maintain the zero-cost nature of futures. With that approach, we'd have the ergonomics of languages like Erlang and Go without the significant performance tax associated with M:N.
- saynsedit 10y agoAre libmill and Go's approach the same? I.e. Holding concurrent state in multiple stack frames? What causes that to have degraded performance? Is this new approach different because instead of stack frames you just have dynamically allocated callback state?
- pcwalton 10y ago> Are libmill and Go's approach the same? I.e. Holding concurrent state in multiple stack frames? What causes that to have degraded performance? In the M:N approach you have to allocate stack space for each goroutine that you spawn. This requires that you either know the size of the stack up front (generally not possible without being conservative and requesting a large allocation) or that you start small and grow (resulting in a lot of memory traffic and pauses in the growth case, and much harder to do in C). By contrast, with the zero-cost futures approach we statically know exactly how much per-goroutine size we will ever need, and we can allocate precisely that amount. Furthermore, we only save the data that's absolutely needed across blocking calls. This results in much smaller per-connection state, and as a result it's quicker to allocate. It's the difference between static and dynamic control flow. Full M:N requires us to give up static knowledge of what a goroutine will do and try to do the best we can at runtime. With futures, we have a lot more static knowledge, and as a result we can optimize more aggressively.
- mindslight 10y ago> This requires that you either know the size of the stack up front ... generally not possible without being conservative Well, you are writing the compiler. Sure you'd be up against the halting problem, but relatively few functions are (non-tail) recursive. Perhaps the unwieldiness of a large stack is better attributed to the feature of unbounded recursion (and FFI into "uncharted territory") than the feature of green threads. I appreciate that Rust has already been down the road of lightweight threads. This statement just struck me as an assumption that deserved to be questioned.
- pcwalton 10y agoIt would require higher order control flow analysis like k-CFA, which would certainly fail to produce a bounded stack size on any nontrivial program. The futures library is the control flow analysis. Because it uses the type system instead of higher order control flow analysis, it actually achieves precision.
- mindslight 10y agoI was thinking of a much lighter analysis, based on some optional restrictions. If a function: - is not recursive - does not call function pointers (ie trait objects) - allocates only fixed-sized objects on the stack - only calls functions with known stack requirements then its stack requirement should be known, no? It feels like with these requirements, one can still write many programs (threads, really). And if one goes beyond the restrictions, then they just pay the cost of having to guess a large stack size. Stack size analysis would be helpful for other applications as well, like embedded platforms.
- pcwalton 10y agoNo program beyond the most trivial will meet these requirements. The moment you call any function indirectly you lose.
- 10y ago
- Matthias247 10y agolibmill only uses a single kernel thread so it's a little bit simpler here than Go, but otherwise they should be comparable. Regarding stack vs. dynamically allocated state (in a future) I'm not convinced what's better. Yes, the allocated stack most likely has an overhead. But at least the data will be stored there in linear fashion and accessing the state will be super cheap once the stack is loaded. In case of futures that hold dynamically allocated data the state might be spread over much more memory locations, so it might be slower to access. I however haven't any more scientific data on this. In my real-world applications I'm getting about the same performance from a Go based and a boost asio (uses no futures but dynamically allocated callback closures for storing state) networking application. However the programming style is completely different.
- saynsedit 10y agoWith Rust zero cost futures, only one memory allocation is done for the entire chain of futures. It's not doing an allocation for every callback. This makes it comparable to using a stack. In an M:N solution, the stack is usually (but not always) allocated similarly (e.g. using malloc).
- sebcat 10y ago> I don't want M:N threading as Go implements it. As someone who writes code for enterpricey businesses doing a lot of I/O bound stuff, golang style M:N threading is a godsend over Java's standard library, and other common platforms in that space. Being able to express your code in a sequential manor and still gain the performance offered by implicit fiber,goroutine,w/e scheduling is pretty awesome. With futures, there's still some language semantic overhead from the point of view of the developer. In my problem space, M:N threading is simply worth it. And if I need anything better performing, I can still switch to C for specific use cases.
- steveklabnik 10y agoWell, one of the problems with M:N threading is that the "switch into C for that part" gets a lot more expensive, negating the speed increase.
- pcwalton 10y ago> Being able to express your code in a sequential manor and still gain the performance offered by implicit fiber,goroutine,w/e scheduling is pretty awesome. You don't gain as much performance. On Linux, you don't actually gain that much if anything over 1:1 threading. Most of the benefits of goroutines actually comes from the small stacks, which don't have anything to do with M:N and 1:1 to begin with—they're a feature of GC. As the blog post states, our end goal is to achieve the ergonomics of Go-style M:N without sacrificing nginx levels of performance. With this futures library, we've established the foundations. It would make no sense to give up before even trying. > And if I need anything better performing, I can still switch to C for specific use cases. Why would you write networking code in C in 2016, when there are better alternatives available (like this one)? Rust's philosophy is to have both performance and ergonomics. It rejects the idea that optimal performance requires a cumbersome programming model. I'm not about to give up on that.
- derefr 10y ago> On Linux, you don't actually gain that much if anything over 1:1 threading. You use green threads instead of native threads because native threads have space overhead, not because they have time overhead. Attempting to spawn 100k OS threads will do strange things to most kernel scheduling algorithms; they're not optimized for that use-case.
- stuartd 10y ago> ergonomics of languages like Erlang and Go My brain read that as "ergolangmics"
- deleted 10y ago[deleted]
- rdtsc 10y ago> sugar over futures down the road to make it easier to write code that looks blocking but actually isn't. Yes! That is basically what I am after. Currently using Erlang so having to give up cheap isolated processes and message passing to do concurrency would be hard. But I understand that is a different level of abstraction as well (a VM, multi-machine clusters, etc vs almost raw C speed). But in general bringing Rust and Erlang together seems like a swell idea. Both are focused on safety, which I like. Been keeping my eye on https://github.com/hansihe/Rustler https://github.com/hansihe/Rustler
- Thaxll 10y agoWhat is the "significant performance tax" in Go? Do you have any numbers about this implementation? When I look at the benchmark of your minihttp vs Go fasthttp, Rust is supposed to be 3x time faster without any GC and yet minihttp pulls 2M req/sec VS 1.5 for Go which is a small margin when you know how Go works and is GC.
- pcwalton 10y ago> What is the "significant performance tax" in Go? I explain in this sibling comment: https://news.ycombinator.com/item?id=12271194 https://news.ycombinator.com/item?id=12271194 > Do you have any numbers about this implementation? See the blog post.
- Thaxll 10y agoI did edit my first question after seeing the numbers.
- pcwalton 10y agoArguing about whether a 30% difference is "significant" isn't really interesting.
- Thaxll 10y agoYou're talking about using a language where you have to manually manage the memory, is significantly harder to use than Go, have less built-in concurrent patterns, inferior standard library, so yes 30% seems a very small benefit. I didn't had look into the benchmark but my guess is that it's waiting on the I/O so any language with good concurrent model will have the same numbers. The benchmark would have been more interesting if it was more CPU intensive, that would have definitely favored Rust. it's not about Rust Vs Go, it's just that 30% seems very low when you know how fast Rust is vs Go in some scenarios.
- pcwalton 10y ago
- yawaramin 10y ago> ... I would like to see async/await style syntactic sugar over futures down the road to make it easier to write code that looks blocking but actually isn't.... Would a Scala-style 'for' comprehension achieve the same thing while also being more generally applicable? Scala achieves this by using instance methods 'map', 'flatMap' and 'withFilter'. Personally I think Rust should just lift the sugar wholesale :-)
- stshine 10y agoSimply comparing M:N threading with futures is not correct. In my experience using futures in Javascript, it does not support control flow at all. The goroutine's equivalent is async/await, which transform the cps of asynchronous I/O back to sync form, but it may impose more overheads than M:N threading since it stores the stack structure in its parameter.
- ngrilly 10y ago> With that approach, we'd have the ergonomics of languages like Erlang and Go without the significant performance tax associated with M:N. Can you quantify the "performance tax associated with M:N"?