Stepping disabled performance fix (#11959)
# Objective * Fixes #11932 (performance impact when stepping is disabled) ## Solution The `Option<FixedBitSet>` argument added to `ScheduleExecutor::run()` in #8453 caused a measurable performance impact even when stepping is disabled. This can be seen by the benchmark of running `Schedule:run()` on an empty schedule in a tight loop (https://github.com/bevyengine/bevy/issues/11932#issuecomment-1950970236). I was able to get the same performance results as on 0.12.1 by changing the argument `ScheduleExecutor::run()` from `Option<FixedBitSet>` to `Option<&FixedBitSet>`. The down-side of this change is that `Schedule::run()` now takes about 6% longer (3.7319 ms vs 3.9855ns) when stepping is enabled --- ## Changelog * Change `ScheduleExecutor::run()` `_skipped_systems` from `Option<FixedBitSet>` to `Option<&FixedBitSet>` * Added a few benchmarks to measure `Schedule::run()` performance with various executors
This commit is contained in:
parent
8de15ae71a
commit
0dccfb5788
@ -19,4 +19,5 @@ criterion_group!(
|
|||||||
contrived,
|
contrived,
|
||||||
schedule,
|
schedule,
|
||||||
build_schedule,
|
build_schedule,
|
||||||
|
empty_schedule_run,
|
||||||
);
|
);
|
||||||
|
@ -118,3 +118,28 @@ pub fn build_schedule(criterion: &mut Criterion) {
|
|||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn empty_schedule_run(criterion: &mut Criterion) {
|
||||||
|
let mut app = bevy_app::App::default();
|
||||||
|
|
||||||
|
let mut group = criterion.benchmark_group("run_empty_schedule");
|
||||||
|
|
||||||
|
let mut schedule = Schedule::default();
|
||||||
|
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
|
||||||
|
group.bench_function("SingleThreaded", |bencher| {
|
||||||
|
bencher.iter(|| schedule.run(&mut app.world));
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut schedule = Schedule::default();
|
||||||
|
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::MultiThreaded);
|
||||||
|
group.bench_function("MultiThreaded", |bencher| {
|
||||||
|
bencher.iter(|| schedule.run(&mut app.world));
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut schedule = Schedule::default();
|
||||||
|
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::Simple);
|
||||||
|
group.bench_function("Simple", |bencher| {
|
||||||
|
bencher.iter(|| schedule.run(&mut app.world));
|
||||||
|
});
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
@ -21,8 +21,8 @@ pub(super) trait SystemExecutor: Send + Sync {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
schedule: &mut SystemSchedule,
|
schedule: &mut SystemSchedule,
|
||||||
skip_systems: Option<FixedBitSet>,
|
|
||||||
world: &mut World,
|
world: &mut World,
|
||||||
|
skip_systems: Option<&FixedBitSet>,
|
||||||
);
|
);
|
||||||
fn set_apply_final_deferred(&mut self, value: bool);
|
fn set_apply_final_deferred(&mut self, value: bool);
|
||||||
}
|
}
|
||||||
|
@ -166,8 +166,8 @@ impl SystemExecutor for MultiThreadedExecutor {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
schedule: &mut SystemSchedule,
|
schedule: &mut SystemSchedule,
|
||||||
_skip_systems: Option<FixedBitSet>,
|
|
||||||
world: &mut World,
|
world: &mut World,
|
||||||
|
_skip_systems: Option<&FixedBitSet>,
|
||||||
) {
|
) {
|
||||||
// reset counts
|
// reset counts
|
||||||
self.num_systems = schedule.systems.len();
|
self.num_systems = schedule.systems.len();
|
||||||
@ -189,26 +189,18 @@ impl SystemExecutor for MultiThreadedExecutor {
|
|||||||
// If stepping is enabled, make sure we skip those systems that should
|
// If stepping is enabled, make sure we skip those systems that should
|
||||||
// not be run.
|
// not be run.
|
||||||
#[cfg(feature = "bevy_debug_stepping")]
|
#[cfg(feature = "bevy_debug_stepping")]
|
||||||
if let Some(mut skipped_systems) = _skip_systems {
|
if let Some(skipped_systems) = _skip_systems {
|
||||||
debug_assert_eq!(skipped_systems.len(), self.completed_systems.len());
|
debug_assert_eq!(skipped_systems.len(), self.completed_systems.len());
|
||||||
// mark skipped systems as completed
|
// mark skipped systems as completed
|
||||||
self.completed_systems |= &skipped_systems;
|
self.completed_systems |= skipped_systems;
|
||||||
self.num_completed_systems = self.completed_systems.count_ones(..);
|
self.num_completed_systems = self.completed_systems.count_ones(..);
|
||||||
|
|
||||||
// signal the dependencies for each of the skipped systems, as
|
// signal the dependencies for each of the skipped systems, as
|
||||||
// though they had run
|
// though they had run
|
||||||
for system_index in skipped_systems.ones() {
|
for system_index in skipped_systems.ones() {
|
||||||
self.signal_dependents(system_index);
|
self.signal_dependents(system_index);
|
||||||
|
self.ready_systems.set(system_index, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, we need to clear all skipped systems from the ready
|
|
||||||
// list.
|
|
||||||
//
|
|
||||||
// We invert the skipped system mask to get the list of systems
|
|
||||||
// that should be run. Then we bitwise AND it with the ready list,
|
|
||||||
// resulting in a list of ready systems that aren't skipped.
|
|
||||||
skipped_systems.toggle_range(..);
|
|
||||||
self.ready_systems &= skipped_systems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let thread_executor = world
|
let thread_executor = world
|
||||||
|
@ -33,15 +33,15 @@ impl SystemExecutor for SimpleExecutor {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
schedule: &mut SystemSchedule,
|
schedule: &mut SystemSchedule,
|
||||||
_skip_systems: Option<FixedBitSet>,
|
|
||||||
world: &mut World,
|
world: &mut World,
|
||||||
|
_skip_systems: Option<&FixedBitSet>,
|
||||||
) {
|
) {
|
||||||
// If stepping is enabled, make sure we skip those systems that should
|
// If stepping is enabled, make sure we skip those systems that should
|
||||||
// not be run.
|
// not be run.
|
||||||
#[cfg(feature = "bevy_debug_stepping")]
|
#[cfg(feature = "bevy_debug_stepping")]
|
||||||
if let Some(skipped_systems) = _skip_systems {
|
if let Some(skipped_systems) = _skip_systems {
|
||||||
// mark skipped systems as completed
|
// mark skipped systems as completed
|
||||||
self.completed_systems |= &skipped_systems;
|
self.completed_systems |= skipped_systems;
|
||||||
}
|
}
|
||||||
|
|
||||||
for system_index in 0..schedule.systems.len() {
|
for system_index in 0..schedule.systems.len() {
|
||||||
|
@ -41,15 +41,15 @@ impl SystemExecutor for SingleThreadedExecutor {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
schedule: &mut SystemSchedule,
|
schedule: &mut SystemSchedule,
|
||||||
_skip_systems: Option<FixedBitSet>,
|
|
||||||
world: &mut World,
|
world: &mut World,
|
||||||
|
_skip_systems: Option<&FixedBitSet>,
|
||||||
) {
|
) {
|
||||||
// If stepping is enabled, make sure we skip those systems that should
|
// If stepping is enabled, make sure we skip those systems that should
|
||||||
// not be run.
|
// not be run.
|
||||||
#[cfg(feature = "bevy_debug_stepping")]
|
#[cfg(feature = "bevy_debug_stepping")]
|
||||||
if let Some(skipped_systems) = _skip_systems {
|
if let Some(skipped_systems) = _skip_systems {
|
||||||
// mark skipped systems as completed
|
// mark skipped systems as completed
|
||||||
self.completed_systems |= &skipped_systems;
|
self.completed_systems |= skipped_systems;
|
||||||
}
|
}
|
||||||
|
|
||||||
for system_index in 0..schedule.systems.len() {
|
for system_index in 0..schedule.systems.len() {
|
||||||
|
@ -333,15 +333,18 @@ impl Schedule {
|
|||||||
.unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label));
|
.unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label));
|
||||||
|
|
||||||
#[cfg(not(feature = "bevy_debug_stepping"))]
|
#[cfg(not(feature = "bevy_debug_stepping"))]
|
||||||
let skip_systems = None;
|
self.executor.run(&mut self.executable, world, None);
|
||||||
|
|
||||||
#[cfg(feature = "bevy_debug_stepping")]
|
#[cfg(feature = "bevy_debug_stepping")]
|
||||||
|
{
|
||||||
let skip_systems = match world.get_resource_mut::<Stepping>() {
|
let skip_systems = match world.get_resource_mut::<Stepping>() {
|
||||||
None => None,
|
None => None,
|
||||||
Some(mut stepping) => stepping.skipped_systems(self),
|
Some(mut stepping) => stepping.skipped_systems(self),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.executor.run(&mut self.executable, skip_systems, world);
|
self.executor
|
||||||
|
.run(&mut self.executable, world, skip_systems.as_ref());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes any newly-added systems and conditions, rebuilds the executable schedule,
|
/// Initializes any newly-added systems and conditions, rebuilds the executable schedule,
|
||||||
|
Loading…
Reference in New Issue
Block a user