Live data from Hacker News

Zero-cost futures in Rust

aturon.github.io

211–220 of 348 posts

Re: Zero-cost futures in Rust

#211

Earlier quoted context omitted.

No. they are not two different points. You are artificially restricting the argument, and saying that some parts are a different question by introducing this new idea of "inherent nature" of a closure in rust : Some humans have legs, some do not. Does it mean that having legs is not an "inherent" part of the human experience? So to go back to the argument, we are trying to compare using a coroutine base approach vs u…

To use your analogy, I feel like you're saying that only humans that have legs are human. I'm saying that they're all human. This analogy is weird. Furthermore, because closures aren't inherently boxed, there might not even be a stack frame to begin with. Closures are syntax sugar for a struct + a trait implementation on that struct, and said call is then very inline-able. This is also where I'm getting at with the s…

I think this is becoming a semantic argument.

I think we all understand that Rust/C++ closures don't necessarily allocate memory. His point is simply that by using a rust closure to asynchronously handle an event a heap allocation must be done. Compare to the stack swapping method of classical coroutines, which wouldn't require a heap allocation.

I recognize that with Rust zero-cost futures, one big heap allocation is done for the entire futures combination and is roughly analogous to one big eager stack allocation for a coroutine.

Re: Zero-cost futures in Rust

#212

Earlier quoted context omitted.

Well, one advantage the Go runtime has is the green threads are cooperatively scheduled, so switches are a lot more lightweight.

> Well, one advantage the Go runtime has is the green threads are cooperatively scheduled, so switches are a lot more lightweight. Switches occur on timeouts (which involve a round trip through the kernel), on I/O events (which also involve a round trip through the kernel), or on goroutine message passing. So in every case except goroutine message sends, the switches don't actually save you a trip through the kernel.

Couldn't you switch on timeout using some kind of CPU counter, e.g. RDTSC?

Re: Zero-cost futures in Rust

#213

Earlier quoted context omitted.

Why would you expect that? AFAIK the Go compiler has a worse optimizer, a worse GC (at least throughput-wise) and possibly also a worse threading runtime. Since the benchmark measures steady-state performance and not memory or latency, and with the best Java server (probably optimized to almost never allocate) then Java having better code generation and threading could give it that much of an edge.

Actually, optimizer, GC and a threading runtime have almost nothing to do with Go's net/http performing that badly. The reasons are mostly in very poor design choices in the library itself that forced them to do a lot of implicit synchronization, unnecessary memory copying, unnecessary system calls, etc. It's just a huge mess, avoid it whenever possible.

Yah I know, both me and the parent comment were talking about fasthttp vs the fastest Java web server. Both of which are likely optimized like crazy to avoid synchronization and allocation as much as possible.

Re: Zero-cost futures in Rust

#214

Earlier quoted context omitted.

To use your analogy, I feel like you're saying that only humans that have legs are human. I'm saying that they're all human. This analogy is weird. Furthermore, because closures aren't inherently boxed, there might not even be a stack frame to begin with. Closures are syntax sugar for a struct + a trait implementation on that struct, and said call is then very inline-able. This is also where I'm getting at with the s…

I think this is becoming a semantic argument. I think we all understand that Rust/C++ closures don't necessarily allocate memory. His point is simply that by using a rust closure to asynchronously handle an event a heap allocation must be done. Compare to the stack swapping method of classical coroutines, which wouldn't require a heap allocation. I recognize that with Rust zero-cost futures, one big heap allocation i…

Yes, I think so too. I just want to make sure the semantics are clear. I think your summary here is excellent.

Re: Zero-cost futures in Rust

#215
post #84

As mentioned in the post, given Rust wants to operate in the same space as C, this approach makes sense. However from a higher level, building more complex concurrent systems, dealing with futures/deferred-s/promises and/or a central select/epoll/kqueue reactor loop gets daunting and doesn't mix with complex business rules. Deferred based approach has been a round for many years. I experienced it by using Twisted (Py…

> 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…

> ... 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 :-)

Re: Zero-cost futures in Rust

#216
I'm genuinely interested in knowing what the problem is with an event loop using epoll and a threadpool for IO that blocks but epoll can't poll. I've used proprietary event loops at 2 giant companies, libuv with C, asio with cpp and nodes async, and the async IO was never the problem in terms of performance or complexity. What is the problem that's trying to be solved?

Re: Zero-cost futures in Rust

#217

Earlier quoted context omitted.

Well, one advantage the Go runtime has is the green threads are cooperatively scheduled, so switches are a lot more lightweight.

> Well, one advantage the Go runtime has is the green threads are cooperatively scheduled, so switches are a lot more lightweight. Switches occur on timeouts (which involve a round trip through the kernel), on I/O events (which also involve a round trip through the kernel), or on goroutine message passing. So in every case except goroutine message sends, the switches don't actually save you a trip through the kernel.

System call context switching is cheaper than and different from thread/scheduler context switching.

Re: Zero-cost futures in Rust

#218

This is awesome, but I have an off-topic rust question: Why can't we have some syntactic sugar to get rid of .unwrap()?

The try! macro should be used much more often than .unwrap(). And there is a feature that has been implemented to make a ? operator that is roughly equivalent to the try macro. It's currently hidden behind a feature gate named question_mark and it's unclear if or when it will be part of the language.

Re: Zero-cost futures in Rust

#219

Earlier quoted context omitted.

Defaults matter. If some people use async I/O and others don't then you get a mess when they want to share reusable libraries. It's similar to the mess you get when there is more than one string type. I think the "what color is your function" problem could be mostly solved by making async/await the default function type - that is, most callback functions should be allowed to suspend execution by waiting on a Future.…

> (Unfortunately, the default has to be the opposite in browser-based languages due to performance concerns.) Also in Rust. Most apps that aren't servers don't want async I/O, and it causes a lot of problems when you need high-performance FFI. For example, in Servo async-everywhere would be a big problem for WebRender, which needs to be able to call OpenGL extremely quickly. > Defaults matter. If some people use asyn…

Easy and error prone.

Sometimes libraries pretend to be async and accidentally are sync. Consider some library that in the normal case just does some pure computation, but logs to syslog or something on some error condition. If you use that library in an async context, it could work fine most of the time, until you hit some unexpected situation where it happens to make a network request to some syslog daemon and blocks up your worker thread. The same thing can happen with mutexes, or many other common blocking operations.

It's also the case that often async libraries depend on some sync library and so they have their own worker pool. You can easily have many libraries with their own worker pools all using more resources then they need.

You also have to worry about if your functions do any of these transformations under the hood. For example, if you have some async worker that delegates some sync task to the worker pool, and that sync task happens to use some async function and blocks on it, and that async function ALSO has a sync task and attempts to delegate it to a worker pool, and that worker pool is bounded, then you have just opened yourself up to a deadlock under high load that you probably won't find under normal operation.

On top of all that, debugging is usually much harder in these environments because you have to inspect the runtime memory state that depends on the specific internal details of the libraries being used instead of a bunch of stacks. It's extremely hard to diagnose deadlocks or stalls in these environments. It's non-trivial to provide a good debugging experience that doesn't cause extra load in production environments.

These issues are all real things that I have hit in production with Twisted. A static type system could help all these things, but I think it requires buy in from every library you might use, transitively.

Re: Zero-cost futures in Rust

#220

Earlier quoted context omitted.

Defaults matter. If some people use async I/O and others don't then you get a mess when they want to share reusable libraries. It's similar to the mess you get when there is more than one string type. I think the "what color is your function" problem could be mostly solved by making async/await the default function type - that is, most callback functions should be allowed to suspend execution by waiting on a Future.…

> (Unfortunately, the default has to be the opposite in browser-based languages due to performance concerns.) Also in Rust. Most apps that aren't servers don't want async I/O, and it causes a lot of problems when you need high-performance FFI. For example, in Servo async-everywhere would be a big problem for WebRender, which needs to be able to call OpenGL extremely quickly. > Defaults matter. If some people use asyn…

> Also in Rust. Most apps that aren't servers don't want async I/O, and it causes a lot of problems when you need high-performance FFI. For example, in Servo async-everywhere would be a big problem for WebRender, which needs to be able to call OpenGL extremely quickly.

I don't understand why this is the case. Since async/await allows the compiler to transform the code into a state machine, why would it be not be able to optimize this?

Post reply on HN