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::{
|
use bevy_ecs::{
|
||||||
component::{Component, Mutable},
|
component::{Component, Mutable},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
lifecycle::Insert,
|
||||||
|
observer::{Observer, On},
|
||||||
prelude::{Added, Changed, EntityWorldMut, QueryState},
|
prelude::{Added, Changed, EntityWorldMut, QueryState},
|
||||||
query::QueryFilter,
|
query::QueryFilter,
|
||||||
world::World,
|
world::World,
|
||||||
@ -17,7 +19,12 @@ criterion_group!(
|
|||||||
all_changed_detection,
|
all_changed_detection,
|
||||||
few_changed_detection,
|
few_changed_detection,
|
||||||
none_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 {
|
macro_rules! modify {
|
||||||
@ -39,6 +46,14 @@ struct Sparse(f32);
|
|||||||
#[component(storage = "Table")]
|
#[component(storage = "Table")]
|
||||||
struct Data<const X: u16>(f32);
|
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 {
|
trait BenchModify {
|
||||||
fn bench_modify(&mut self) -> f32;
|
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];
|
const ENTITIES_TO_BENCH_COUNT: &[u32] = &[5000, 50000];
|
||||||
|
|
||||||
type BenchGroup<'a> = criterion::BenchmarkGroup<'a, criterion::measurement::WallTime>;
|
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)
|
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
|
// 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> {
|
fn generic_filter_query<F: QueryFilter>(world: &mut World) -> QueryState<Entity, F> {
|
||||||
world.query_filtered::<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) {
|
fn insert_if_bit_enabled<const B: u16>(entity: &mut EntityWorldMut, i: u16) {
|
||||||
if i & (1 << B) != 0 {
|
if i & (1 << B) != 0 {
|
||||||
entity.insert(Data::<B>(1.0));
|
entity.insert(Data::<B>(1.0));
|
||||||
|
Loading…
Reference in New Issue
Block a user