Bench replacement of immutable components with hooks
This commit is contained in:
parent
58c276ab44
commit
e39fa12ee5
@ -3,6 +3,8 @@ use core::hint::black_box;
|
||||
use bevy_ecs::{
|
||||
component::{Component, Mutable},
|
||||
entity::Entity,
|
||||
lifecycle::Insert,
|
||||
observer::{Observer, On},
|
||||
prelude::{Added, Changed, EntityWorldMut, QueryState},
|
||||
query::QueryFilter,
|
||||
world::World,
|
||||
@ -17,7 +19,12 @@ criterion_group!(
|
||||
all_changed_detection,
|
||||
few_changed_detection,
|
||||
none_changed_detection,
|
||||
multiple_archetype_none_changed_detection
|
||||
multiple_archetype_none_changed_detection,
|
||||
all_immutable_replace_detection,
|
||||
few_immutable_replace_detection,
|
||||
none_immutable_replace_detection,
|
||||
all_immutable_replace_no_observer,
|
||||
few_immutable_replace_no_observer
|
||||
);
|
||||
|
||||
macro_rules! modify {
|
||||
@ -39,6 +46,14 @@ struct Sparse(f32);
|
||||
#[component(storage = "Table")]
|
||||
struct Data<const X: u16>(f32);
|
||||
|
||||
#[derive(Component, Default, Clone)]
|
||||
#[component(storage = "Table", immutable)]
|
||||
struct ImmutableTable(f32);
|
||||
|
||||
#[derive(Component, Default, Clone)]
|
||||
#[component(storage = "SparseSet", immutable)]
|
||||
struct ImmutableSparse(f32);
|
||||
|
||||
trait BenchModify {
|
||||
fn bench_modify(&mut self) -> f32;
|
||||
}
|
||||
@ -56,6 +71,22 @@ impl BenchModify for Sparse {
|
||||
}
|
||||
}
|
||||
|
||||
trait BenchReplace {
|
||||
fn bench_replace(&self) -> Self;
|
||||
}
|
||||
|
||||
impl BenchReplace for ImmutableTable {
|
||||
fn bench_replace(&self) -> Self {
|
||||
Self(self.0 + 1f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl BenchReplace for ImmutableSparse {
|
||||
fn bench_replace(&self) -> Self {
|
||||
Self(self.0 + 1f32)
|
||||
}
|
||||
}
|
||||
|
||||
const ENTITIES_TO_BENCH_COUNT: &[u32] = &[5000, 50000];
|
||||
|
||||
type BenchGroup<'a> = criterion::BenchmarkGroup<'a, criterion::measurement::WallTime>;
|
||||
@ -70,6 +101,17 @@ fn setup<T: Component + Default>(entity_count: u32) -> World {
|
||||
black_box(world)
|
||||
}
|
||||
|
||||
fn setup_with_observer<T: Component + Default + Clone + 'static>(entity_count: u32) -> World {
|
||||
let mut world = World::default();
|
||||
|
||||
world.spawn(Observer::new(|trigger: On<Insert, T>| {
|
||||
black_box(trigger);
|
||||
}));
|
||||
|
||||
world.spawn_batch((0..entity_count).map(|_| T::default()));
|
||||
black_box(world)
|
||||
}
|
||||
|
||||
// create a cached query in setup to avoid extra costs in each iter
|
||||
fn generic_filter_query<F: QueryFilter>(world: &mut World) -> QueryState<Entity, F> {
|
||||
world.query_filtered::<Entity, F>()
|
||||
@ -266,6 +308,249 @@ fn none_changed_detection(criterion: &mut Criterion) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn all_immutable_replace_detection_generic<
|
||||
T: Component + Default + Clone + BenchReplace + 'static,
|
||||
>(
|
||||
group: &mut BenchGroup,
|
||||
entity_count: u32,
|
||||
) {
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = setup_with_observer::<T>(entity_count);
|
||||
let mut query = world.query::<Entity>();
|
||||
let entities: Vec<_> = query.iter(&world).collect();
|
||||
(world, entities)
|
||||
},
|
||||
|(world, entities)| {
|
||||
for entity in entities {
|
||||
let component = world
|
||||
.entity(*entity)
|
||||
.get::<T>()
|
||||
.cloned()
|
||||
.unwrap_or_else(T::default);
|
||||
let new_component = component.bench_replace();
|
||||
black_box(world.entity_mut(*entity).insert(new_component));
|
||||
}
|
||||
},
|
||||
criterion::BatchSize::LargeInput,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn all_immutable_replace_detection(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("all_immutable_replace_detection");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
Box::new(all_immutable_replace_detection_generic::<ImmutableTable>),
|
||||
//Box::new(all_immutable_replace_detection_generic::<ImmutableSparse>),
|
||||
],
|
||||
entity_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn few_immutable_replace_detection_generic<
|
||||
T: Component + Default + Clone + BenchReplace + 'static,
|
||||
>(
|
||||
group: &mut BenchGroup,
|
||||
entity_count: u32,
|
||||
) {
|
||||
let ratio_to_modify = 0.1;
|
||||
let amount_to_modify = (entity_count as f32 * ratio_to_modify) as usize;
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = setup_with_observer::<T>(entity_count);
|
||||
let mut query = world.query::<Entity>();
|
||||
let mut entities: Vec<_> = query.iter(&world).collect();
|
||||
entities.shuffle(&mut deterministic_rand());
|
||||
(world, entities)
|
||||
},
|
||||
|(world, entities)| {
|
||||
for entity in entities[0..amount_to_modify].iter() {
|
||||
let component = world
|
||||
.entity(*entity)
|
||||
.get::<T>()
|
||||
.cloned()
|
||||
.unwrap_or_else(T::default);
|
||||
let new_component = component.bench_replace();
|
||||
black_box(world.entity_mut(*entity).insert(new_component));
|
||||
}
|
||||
},
|
||||
criterion::BatchSize::LargeInput,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn few_immutable_replace_detection(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("few_immutable_replace_detection");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
Box::new(few_immutable_replace_detection_generic::<ImmutableTable>),
|
||||
Box::new(few_immutable_replace_detection_generic::<ImmutableSparse>),
|
||||
],
|
||||
entity_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn none_immutable_replace_detection_generic<T: Component + Default + Clone + 'static>(
|
||||
group: &mut BenchGroup,
|
||||
entity_count: u32,
|
||||
) {
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let world = setup_with_observer::<T>(entity_count);
|
||||
world
|
||||
},
|
||||
|world| {
|
||||
// Do nothing - just measure the overhead of having the observer
|
||||
// without actually replacing any components
|
||||
world.clear_trackers();
|
||||
},
|
||||
criterion::BatchSize::LargeInput,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn none_immutable_replace_detection(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("none_immutable_replace_detection");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
Box::new(none_immutable_replace_detection_generic::<ImmutableTable>),
|
||||
Box::new(none_immutable_replace_detection_generic::<ImmutableSparse>),
|
||||
],
|
||||
entity_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Baseline benchmarks without observers for comparison
|
||||
fn all_immutable_replace_no_observer_generic<
|
||||
T: Component + Default + Clone + BenchReplace + 'static,
|
||||
>(
|
||||
group: &mut BenchGroup,
|
||||
entity_count: u32,
|
||||
) {
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = setup::<T>(entity_count); // No observer
|
||||
let mut query = world.query::<Entity>();
|
||||
let entities: Vec<_> = query.iter(&world).collect();
|
||||
(world, entities)
|
||||
},
|
||||
|(world, entities)| {
|
||||
for entity in entities {
|
||||
let component = world
|
||||
.entity(*entity)
|
||||
.get::<T>()
|
||||
.cloned()
|
||||
.unwrap_or_else(T::default);
|
||||
let new_component = component.bench_replace();
|
||||
black_box(world.entity_mut(*entity).insert(new_component));
|
||||
}
|
||||
},
|
||||
criterion::BatchSize::LargeInput,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn all_immutable_replace_no_observer(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("all_immutable_replace_no_observer");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
Box::new(all_immutable_replace_no_observer_generic::<ImmutableTable>),
|
||||
Box::new(all_immutable_replace_no_observer_generic::<ImmutableSparse>),
|
||||
],
|
||||
entity_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn few_immutable_replace_no_observer_generic<
|
||||
T: Component + Default + Clone + BenchReplace + 'static,
|
||||
>(
|
||||
group: &mut BenchGroup,
|
||||
entity_count: u32,
|
||||
) {
|
||||
let ratio_to_modify = 0.1;
|
||||
let amount_to_modify = (entity_count as f32 * ratio_to_modify) as usize;
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = setup::<T>(entity_count); // No observer
|
||||
let mut query = world.query::<Entity>();
|
||||
let mut entities: Vec<_> = query.iter(&world).collect();
|
||||
entities.shuffle(&mut deterministic_rand());
|
||||
(world, entities)
|
||||
},
|
||||
|(world, entities)| {
|
||||
for entity in entities[0..amount_to_modify].iter() {
|
||||
let component = world
|
||||
.entity(*entity)
|
||||
.get::<T>()
|
||||
.cloned()
|
||||
.unwrap_or_else(T::default);
|
||||
let new_component = component.bench_replace();
|
||||
black_box(world.entity_mut(*entity).insert(new_component));
|
||||
}
|
||||
},
|
||||
criterion::BatchSize::LargeInput,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn few_immutable_replace_no_observer(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("few_immutable_replace_no_observer");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
Box::new(few_immutable_replace_no_observer_generic::<ImmutableTable>),
|
||||
Box::new(few_immutable_replace_no_observer_generic::<ImmutableSparse>),
|
||||
],
|
||||
entity_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_if_bit_enabled<const B: u16>(entity: &mut EntityWorldMut, i: u16) {
|
||||
if i & (1 << B) != 0 {
|
||||
entity.insert(Data::<B>(1.0));
|
||||
|
Loading…
Reference in New Issue
Block a user