How Does Increasing Thread Count Impact the Performance of Rust Executors?

I play around with async in Rust and wonder how the number of threads would affect performance. If I understand correctly: in case the executor polls each future one at a time only one part of my code is running on given thread at any moment.

How much performance gain can I expect from adding more threads in this context? Because executor only ever polls one future at a time, does that mean additional threads provide no value?

What happens if some future does some computation which is heavy and does not yield, for example, does not use .await? As far as I understand this can block an executor as it can't switch to other futures in the meantime. Is there anything that would be done about such blocking tasks more effectively?

Am I mistaken, or am I missing something about how executors actually work?

This is of course runtime-dependent, but only ever polling one future if you have multiple threads available that could poll other futures simultaneously would be pretty wasteful. That's why runtimes like Tokio's multi-threaded one poll tasks from each worker thread. You can read more about the runtime here:

That's correct, a task that doesn't yield prevents other tasks and the IO and time drivers from being scheduled, which is problematic. Mandatory link to Alice's post on blocking, which contains a list of ways to deal with CPU-bound tasks the right way:

Also of interest:

2 Likes

To confirm my understanding: the executor is polling tasks across a number of threads in parallel, but within each individual thread, it's polling one task at a time. If a task doesn't yield control, then it blocks all the other tasks that have been assigned to that thread. Thank you for your response!

Yes, kind of. Tokio implements work stealing, so before an idle worker thread goes to sleep, it checks the local task queue of other worker threads (if the global queue is empty, too), trying to snatch one of their tasks.

1 Like