Immediately poll the executor once before spawning it as a task (#11801)

# Objective
At the start of every schedule run, there's currently a guaranteed piece
of overhead as the async executor spawns the MultithreadeExecutor task
onto one of the ComputeTaskPool threads.

## Solution
Poll the executor once to immediately schedule systems without waiting
for the async executor, then spawn the task if and only if the executor
does not immediately terminate.

On a similar note, having the executor task immediately start executing
a system in the same async task might yield similar results over a
broader set of cases. However, this might be more involved, and may need
a solution like #8304.
This commit is contained in:
James Liu 2024-02-12 07:33:35 -08:00 committed by GitHub
parent bd25135330
commit 87add5660f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 13 additions and 5 deletions

View file

@ -3,7 +3,7 @@ use std::{
sync::{Arc, Mutex},
};
use bevy_tasks::{ComputeTaskPool, Scope, TaskPool, ThreadExecutor};
use bevy_tasks::{block_on, poll_once, ComputeTaskPool, Scope, TaskPool, ThreadExecutor};
use bevy_utils::default;
use bevy_utils::syncunsafecell::SyncUnsafeCell;
#[cfg(feature = "trace")]
@ -227,7 +227,8 @@ impl SystemExecutor for MultiThreadedExecutor {
|scope| {
// the executor itself is a `Send` future so that it can run
// alongside systems that claim the local thread
let executor = async {
#[allow(unused_mut)]
let mut executor = Box::pin(async {
let world_cell = world.as_unsafe_world_cell();
while self.num_completed_systems < self.num_systems {
// SAFETY:
@ -252,13 +253,19 @@ impl SystemExecutor for MultiThreadedExecutor {
self.rebuild_active_access();
}
}
};
});
#[cfg(feature = "trace")]
let executor_span = info_span!("multithreaded executor");
#[cfg(feature = "trace")]
let executor = executor.instrument(executor_span);
scope.spawn(executor);
let mut executor = executor.instrument(executor_span);
// Immediately poll the task once to avoid the overhead of the executor
// and thread wake-up. Only spawn the task if the executor does not immediately
// terminate.
if block_on(poll_once(&mut executor)).is_none() {
scope.spawn(executor);
}
},
);

View file

@ -30,6 +30,7 @@ pub use thread_executor::{ThreadExecutor, ThreadExecutorTicker};
pub use async_io::block_on;
#[cfg(not(feature = "async-io"))]
pub use futures_lite::future::block_on;
pub use futures_lite::future::poll_once;
mod iter;
pub use iter::ParallelIterator;