Why shouldn't I use Tokio for my High-CPU workloads?

The async_executor crate they used ( which comes from the smol ecosystem ) is a really fast executor too, and it is much smaller, which is great for Bevy. async_executor allows you to essentially build your own executor tailored to your needs, which is really cool. I'd personally prefer smol over tokio when I have a choice because I like the trimmed down design and small dependency size.

It's also got a work-stealing scheduler like Tokio I think.

Yeah, sounds like that's exactly it:

Some discussion here:
Task System for Bevy · Issue #318 · bevyengine/bevy (github.com)

Looks like they didn't really seriously look into Tokio, just Rayon. But they're very dependency-shy as a group, so Tokio is probably too much bloat for them. Additionally, they want to be able to customize the scheduler for a game workload, and have a much more structured set of tasks than I plan to. Makes sense.

1 Like

I usually called it an "uncooperative task," (or thread) some people called it "failure to yield" (see 4.3, von Behren), but that's in the world of cooperative user-level threads. I do know of one community that uses blocking in this way: the embedded world when MCUs perform polling I/O in a spinloop - they call this blocking I/O (here it's the CPU that's being blocked). In the node.js and/or GUI world, I've seen the use as a transitive verb as in "blocking the event loop" or "blocking the UI thread." (so this would speak in favor of the extended use, but with the object.)

BTW, even the tokio documentation usually draws slightly more of a distinction, like when talking about "CPU-bound tasks and blocking code" (as opposed to just "blocking code"). It even advises against using spawn_blocking for purely CPU-bound tasks, at least when concurrency throttling is desired, and suggests rayon instead.

In addition, unlike for I/O blocking, the definition is much more difficult to make precise. If an I/O operation blocks the current thread, you can - more or less - instantaneously state it, with "blocking" due to CPU use it's a lot more wishy-washy when a task becomes blocking, due to the quantitative assessment of CPU usage involved.

But independent of how you phrase it, it's still different phenomena and (often?) asking for different strategies, so there may be benefit to keeping it more separated.

Hmm...

From the point of view of Tokio there is no difference between a function that gets stuck in a loop on some longe winded calculation for a long time and a function that gets stuck on a blocking I/O operation like a POSIX style read().

They both hang up the thread until they return. They are both "blocking".

There is room for confusion though as the thread they are blocking is now Tokio or whatever async run-time. So it's not just the task that calls such a function that is stalled but all the other async tasks as well.

I agree there is room for confusion in the terminology. But that is often so when terms are used in different contexts where they can have subtle, or not so subtle, difference in nuance.

As far as I can tell the Tokio docs and other writings are pretty consistent and clear in their use of such terminology.

This is true, but it's also the wrong mindset in a way. Tokio's point of view (if tokio were a person) doesn't matter as much as the whole-system point of view because the entire system is designed to do some useful job in its entirety. This end may require tokio to be "blocked" and still have the desired progress (Carl Lerche alludes to this by stating that "progress is hard to define" in those cases.)

The trouble with assigning an all-encompassing, and also perhaps even negative label such as blocking to placing CPU-intensive activity in an async task is that it may often be a perfectly reasonable choice, especially when tokio uses num_cpu working threads. (*) Not always, mind you, but it's nowhere near a don't do as actually engaging in blocking API calls.

(*) this is assuming there's no HOL blocking and the tokio scheduler can poll a future whenever it has an idle worker thread.

1 Like

I agree that having to different definitions of "blocking" is confusing, but it is the terminology that we have ended up with. I did not come up with it when I wrote the article.

All of the resources I've written on blocking recommend rayon for CPU-intensive stuff for this reason. The spawn_blocking pool can spawn hundreds of threads and is therefore not suited for CPU-intensive things. It exists for things like tokio::fs.

The work stealing part of the scheduler should avoid this kind of problem.

1 Like

@JohnAustin Where did you land on this? I happened to be looking for the same info when you made this thread. I ended up going with tokio and making use of spawn_blocking which proved to be faster than any other way I could think of doing things.

I put together a table here. It compares vanilla tokio, tokio with spawn_blocking, futures, and rayon with different numbers of threads and their efficiency in gzip compressing blocks of bytes and writing them to disk.

Tokio with spawn_blocking seems to be the most efficient with the least resources.

Edit: To save future travelers a bunch of reading, I wasn't limiting the number of blocking threads tokio can spawn. It turns out tokio is a bit awkward to configure for my use case, and not in fact more performant than rayon or futures / other flavors of async.

Based on this discussion Tokio seems to be pretty optimal for my use case (heterogeneous high-cpu tasks).

I would expect that if you have homogenous high-cpu tasks like gzip, a dedicated threadpool like Rayon would outperform Tokio. spawn_blocking should be similar to using a Rayon threadpool, if the number of threads is close to the number of cores, and the task chunks are large.

But it'll take me a while to get around to testing my use-case -- I was primarily making sure I wasn't making a mistake in supporting Tokio as a the primary async runner for my system.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.