-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consolidate together Bevy's TaskPools #12090
base: main
Are you sure you want to change the base?
Conversation
d6cbbbb
to
6bb352c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a read over this addresses a long-standing gripe I've had with bevy_tasks WRT thread assignment policies, as a bonus this makes bevy_tasks a bit easier to understand. I can't comment on the rendering side of the changes myself, but this is looking to be on the right track.
There should probably be a spawn_blocking_async method too for convenience. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the reasoning behind the change and also blocking
crate seems pretty good for this.
As someone who often use AsyncComputeTaskPool
for procedural content generation, I think this change is fine, since I prefer the systems to have priority when using all cores available, to make sure FPS doesn't drop too often.
Co-authored-by: Afonso Lage <lage.afonso@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm in favor of this. I argued more or less for this in the linked pr.
Is there an impending fix for the wasm build failure? Would you consider renaming |
Co-authored-by: Mike <mike.hsu@gmail.com>
Fixed it with
I don't want to push that kind of breakage in this PR. The focus here is to get the functionality in place. |
Just noting that I have previously argued for starting with the ‘allow use of all hardware threads/cores’ rather than artificially limiting them and leaving lots of performance on the table and deal with contention problems in other ways. So, I support this. |
Note that this seems to have a significant performance regression as is during app startup. It seems that this change blocks rendering until the entire scene is loaded. Discussion: https://discord.com/channels/691052431525675048/749335865876021248/1211935807099895888 |
I'm on board for this general idea provided we have the numbers to back up the move in the scenarios we care about. I'd like to see comparisons for things like "heavy scene startup", "loading big scene assets while also doing heavy / highly parallel compute tasks in systems", etc. |
Posted these traces in discord a few weeks ago, posting them here so they don't get buried. This was loading a ~1-2GB gltf scene (I don't remember which specific one, it was either bistro with compressed textures or the synty fantasy castle scene, but both traces are for the same scene and iirc bevy main was at the time the same PR that this was branched off of). This PR takes significantly longer to load all the assets and get to a playable state (~7s vs ~4.7s), and doesn't let asset loading happen async with the game running. Had to zip the traces together because github only accepts a few different file types traces.zip |
42a79f3
to
207075e
Compare
@Elabajaba with #12988 merged, it should now show the spans for IO tasks like loading/preprocessing assets. Could you try collecting another trace? If you don't have time, I'll go ahead and check against the Bistro load that I tried before. |
The gltf loader is still using a scope to spawn some tasks. This is necessary since it's borrowing data for some of the tasks. We probably need a scope_blocking, that spawns tasks on the blocking threads for this use case. Another possibility is to somehow throttle the amount of tasks the asset loader is allowed to spawn at once. edit: or maybe a |
Some care needs to be taken to ensure that blocking tasks or scopes are not used from the main task pool, or it'll be trivial to block as seen here. |
@james7132 I don't think this makes sense for the 0.14 milestone in its current state. Removing, but feel free to argue with me if you disagree :) |
Objective
Fixes #1907. Spiritual successor to #4740.
Bevy currently creates a 50%/25%/25% split between the Compute, AsyncCompute, and IO task pools, meaning that any given operation can only be scheduled onto that subset of threads. This is suboptimal in the cases where apps are not using any IO or Async Compute, or vice versa, where available parallelism would be under utilized due to the split not reflecting the actual use case. This PR aims to fix that under utilization.
Solution
TaskPool::spawn_blocking
as an alternative for blocking IO operations and any task that would previously be spawned on the AsyncCompute task pool. This will be backed byblocking
's dynamically scaled thread pool, which will spin up and down threads depending on the need.This allows ECS systems and parallel iterators to be scheduled onto all CPU cores instead of artificially constraining them to half of the logical cores available, as well as for typical async IO tasks to interweave onto any thread.
The use of
spawn_blocking
to perform CPU-bound operations will have to rely on the OS's preemptive scheduler to properly schedule the threads when the main task pool's threads are sitting idle. This comes with potential context switching costs, but is generally preferable to choking out the entire app due the task not cooperatively yielding available threads or artificially limiting available parallelism.Note: We're already using
blocking
throughasync-fs
for loading assets from disk. This shift primarily moves all of the other blocking IO tasks, and the async compute tasks intoblocking
's thread pool.Changelog
Added:
TaskPool::spawn_blocking
Added:
TaskPool::spawn_blocking_async
Removed:
IoTaskPool
Removed:
AsyncComputeTaskPool
Changed:
ComputeTaskPool
by default now spawns a thread for every available logical CPU core.Migration Guide
IoTaskPool
andAsyncComputeTaskPool
have been removed and merged withComputeTaskPool
. ReplaceIoTaskPool::get
andAsyncComputeTaskPool::get
.If you were spawning futures that are reliant on non-blocking IO (as in the task spends most of it's time yielding via
await
), just spawn the task onto theComputeTaskPool
:If you were spawning futures that are reliant on blocking IO (i.e.
std::fs::File::read_to_string
), useComputeTaskPool::spawn_blocking
instead. This will spawn and/or reuse one of the dynamically scaled threads explicitly made for blocking operations.If you were spawning futures for async compute, use
ComputeTaskPool::spawn_blocking
instead. This will spawn and/or reuse one of the dynamically scaled threads explicitly made for blocking operations.If you were spawning futures that are reliant on blocking IO (i.e.
std::fs::File::read_to_string
), useComputeTaskPool::spawn_blocking
instead. This will spawn and/or reuse one of the dynamically scaled threads explicitly made for blocking operations.If you were spawning futures for long running tasks that used both blocking and non-blocking work, use
ComputeTaskPool::spawn_blocking_async
instead.