-
Notifications
You must be signed in to change notification settings - Fork 13
Description
What problem does this solve or what need does it fill?
When a ThreadPool::scope call runs out of jobs from the current scope before the scope is complete, it runs other jobs from the pool. That works well if the jobs are small and uniform, but for Bevy this can cause Query::par_iter() calls to start executing other systems while waiting! If the stolen systems are long and the blocked system has a lot of dependencies, that removes a lot of concurrency from the schedule.
What solution would you like?
Have Scope::complete only run jobs from the current scope. If it runs out of local jobs before the scope is complete, put the thread to sleep instead of running other jobs, so that it can be awakened immediately when the scope is completed.
Then, to make sure the CPUs are all utilized, add a new worker thread to the pool. When the scope is finally complete, remove the thread that completed it to compensate. This will usually be a different thread then the one added.
It may be possible to do this without resizing the pool by sending the WorkerThread value to the new thread.
Additional context
For comparison, here are the approaches taken by some other pools:
- Rayon runs other work. It assumes that jobs are small, so there won't be much delay in waiting.
- async_scoped can be used with Tokio and does something similar to this proposal. It calls
tokio::task::block_in_place, which sends the local queue to a new thread. But if another thread does take the queue, the original one never takes it back! That means it runs one too many threads until the original job completes. It assumes the original job was small and will complete quickly once theblock_in_placecall is done. bevy_taskseither runs other work or blocks, based on thetick_task_pool_executorparameter.- The multi-threaded executor passes
false, blocking the thread. This runs on the main thread, and we want to keep it idle to handle non-send and exclusive systems. - Everything else (including
Query::par_iter()) passestrue, running other work.
- The multi-threaded executor passes