From 3bd039e821c10cc4113deffc4c5a942f4dbba3c8 Mon Sep 17 00:00:00 2001 From: re0312 <45868716+re0312@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:07:20 +0800 Subject: [PATCH] Skip empty archetype/table (#14749) # Objective - As sander commneted on discord [link](https://discord.com/channels/691052431525675048/749335865876021248/1273414144091230228), ![image](https://github.com/user-attachments/assets/62f2b6f3-1aaf-49d9-bafa-bf62b83b10be) ## Performance ![image](https://github.com/user-attachments/assets/11122940-1547-42ae-9576-0e1a93fd9f5f) --------- Co-authored-by: Alice Cecile Co-authored-by: Giacomo Stevanato --- benches/benches/bevy_ecs/benches.rs | 2 + benches/benches/bevy_ecs/fragmentation/mod.rs | 99 +++++++++++++++++++ crates/bevy_ecs/src/query/iter.rs | 17 +++- 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 benches/benches/bevy_ecs/fragmentation/mod.rs diff --git a/benches/benches/bevy_ecs/benches.rs b/benches/benches/bevy_ecs/benches.rs index b3db6abe77..1392536a7d 100644 --- a/benches/benches/bevy_ecs/benches.rs +++ b/benches/benches/bevy_ecs/benches.rs @@ -2,6 +2,7 @@ use criterion::criterion_main; mod components; mod events; +mod fragmentation; mod iteration; mod observers; mod scheduling; @@ -11,6 +12,7 @@ criterion_main!( components::components_benches, events::event_benches, iteration::iterations_benches, + fragmentation::fragmentation_benches, observers::observer_benches, scheduling::scheduling_benches, world::world_benches, diff --git a/benches/benches/bevy_ecs/fragmentation/mod.rs b/benches/benches/bevy_ecs/fragmentation/mod.rs new file mode 100644 index 0000000000..c5b3646221 --- /dev/null +++ b/benches/benches/bevy_ecs/fragmentation/mod.rs @@ -0,0 +1,99 @@ +use bevy_ecs::prelude::*; +use bevy_ecs::system::SystemState; +use criterion::*; +use glam::*; +use std::hint::black_box; + +criterion_group!(fragmentation_benches, iter_frag_empty); + +#[derive(Component, Default)] +struct Table(usize); +#[derive(Component, Default)] +#[component(storage = "SparseSet")] +struct Sparse(usize); + +fn flip_coin() -> bool { + rand::random::() +} +fn iter_frag_empty(c: &mut Criterion) { + let mut group = c.benchmark_group("iter_fragmented(4096)_empty"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + + group.bench_function("foreach_table", |b| { + let mut world = World::new(); + spawn_empty_frag_archetype::(&mut world); + let mut q: SystemState> = + SystemState::)>>::new(&mut world); + let query = q.get(&world); + b.iter(move || { + let mut res = 0; + query.iter().for_each(|(e, t)| { + res += e.to_bits(); + black_box(t); + }); + }); + }); + group.bench_function("foreach_sparse", |b| { + let mut world = World::new(); + spawn_empty_frag_archetype::(&mut world); + let mut q: SystemState> = + SystemState::)>>::new(&mut world); + let query = q.get(&world); + b.iter(move || { + let mut res = 0; + query.iter().for_each(|(e, t)| { + res += e.to_bits(); + black_box(t); + }); + }); + }); + group.finish(); + + fn spawn_empty_frag_archetype(world: &mut World) { + for i in 0..65536 { + let mut e = world.spawn_empty(); + if flip_coin() { + e.insert(Table::<1>(0)); + } + if flip_coin() { + e.insert(Table::<2>(0)); + } + if flip_coin() { + e.insert(Table::<3>(0)); + } + if flip_coin() { + e.insert(Table::<4>(0)); + } + if flip_coin() { + e.insert(Table::<5>(0)); + } + if flip_coin() { + e.insert(Table::<6>(0)); + } + if flip_coin() { + e.insert(Table::<7>(0)); + } + if flip_coin() { + e.insert(Table::<8>(0)); + } + if flip_coin() { + e.insert(Table::<9>(0)); + } + if flip_coin() { + e.insert(Table::<10>(0)); + } + if flip_coin() { + e.insert(Table::<11>(0)); + } + if flip_coin() { + e.insert(Table::<12>(0)); + } + e.insert(T::default()); + + if i != 0 { + e.despawn(); + } + } + } +} diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index f1e724b6c5..f2c74429d6 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -68,6 +68,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { where Func: FnMut(B, D::Item<'w>) -> B, { + if table.is_empty() { + return accum; + } assert!( rows.end <= u32::MAX as usize, "TableRow is only valid up to u32::MAX" @@ -120,6 +123,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { where Func: FnMut(B, D::Item<'w>) -> B, { + if archetype.is_empty() { + return accum; + } let table = self.tables.get(archetype.table_id()).debug_checked_unwrap(); D::set_archetype( &mut self.cursor.fetch, @@ -186,6 +192,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { where Func: FnMut(B, D::Item<'w>) -> B, { + if archetype.is_empty() { + return accum; + } assert!( rows.end <= u32::MAX as usize, "TableRow is only valid up to u32::MAX" @@ -1716,6 +1725,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { if self.current_row == self.current_len { let table_id = self.storage_id_iter.next()?.table_id; let table = tables.get(table_id).debug_checked_unwrap(); + if table.is_empty() { + continue; + } // SAFETY: `table` is from the world that `fetch/filter` were created for, // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with unsafe { @@ -1725,7 +1737,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { self.table_entities = table.entities(); self.current_len = table.entity_count(); self.current_row = 0; - continue; } // SAFETY: set_table was called prior. @@ -1752,6 +1763,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { if self.current_row == self.current_len { let archetype_id = self.storage_id_iter.next()?.archetype_id; let archetype = archetypes.get(archetype_id).debug_checked_unwrap(); + if archetype.is_empty() { + continue; + } let table = tables.get(archetype.table_id()).debug_checked_unwrap(); // SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for, // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with @@ -1772,7 +1786,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { self.archetype_entities = archetype.entities(); self.current_len = archetype.len(); self.current_row = 0; - continue; } // SAFETY: set_archetype was called prior.