3 ms·
Can somebody explain the performance characteristics? For instance, let’s say I have a deep stack of 15 async calls. If the deepest call yields, what code is ex
by giovannibajo1 7y ago
Can somebody explain the performance characteristics? For instance, let’s say I have a deep stack of 15 async calls. If the deepest call yields, what code is executed? Does it “jump back” to the top ala “longjmp” or it needs to return back up the stack frame by frame? And what happens when it resumes execution? IOW is the overhead linear with the stack depth or not?
- Rusky 7y agoYou are correct, the overhead is linear with the stack depth. I suggested a more longjmp-esque approach [here](https://internals.rust-lang.org/t/pre-rfc-cps-transform-for-generators/7120 https://internals.rust-lang.org/t/pre-rfc-cps-transform-for-...) early on, but it didn't seem to be worth the extra implementation effort. Benchmarking never showed this linear overhead to be an issue. The current thinking from the team seems to be that, if this does become an issue, a) inlining before state machine transformation can flatten this out quite a bit and b) convincing LLVM to do what would be required is a lot of work.
- Arnavion 7y agoAlso, you as the user always have the option of manually reducing the depth of the stack - spawn() a separate future with a one-shot channel sender and .await its receiver.
- Arnavion 7y ago>If the deepest call yields, what code is executed? Does it “jump back” to the top ala “longjmp” or it needs to return back up the stack frame by frame? It goes back frame-by-frame. Yielding (ie returning `Poll::Pending`) is essentially returning "I'm not ready yet" and it's possible the parent future wants to do something else in that case. The simplest case is a future that wraps a collection of futures and resolves to the value of the first one that resolves. In this case it would cycle through each child future and only return `Pending` if all of them return `Pending`. Another case is that of a timeout future that wraps another future - it would resolve to `Err(TimedOut)` if the wrapped future returns `Pending` for too long. >And what happens when it resumes execution? The top-most future of the task is all the executor knows about, so the executor has no choice but to start from there.
- giovannibajo1 7y agoThere have been comments in this HN thread that this implementation of continuations based on futures is supposedly more performant than a stack-pointer switching one (like green threads). Can you elaborate on how? It looks to me that it should instead be slower (and even much slower if the stack depth is deep).
- Arnavion 7y agoIt cannot be more "performant". The design of demand-driven futures is used because it allows "zero-cost futures". Specifically poll()ing the Future is required to drive it, and drop()ping the Future frees its resources. Both of these require another future or the executor itself to be the parent of every future, rather than the future driving itself. But also the performance is not likely something to worry about. I've never measured it myself but I do know aturon posted that he could not find any measurable effect of having deep future stacks. It is noticeable in the sense that a big enough stack may exceed your OS's limit (especially on Windows where the main thread defaults to a 1MiB stack).