mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +00:00
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 <alice.i.cecile@gmail.com> Co-authored-by: Giacomo Stevanato <giaco.stevanato@gmail.com>
This commit is contained in:
parent
d3ffb2a5c0
commit
3bd039e821
3 changed files with 116 additions and 2 deletions
|
@ -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,
|
||||
|
|
99
benches/benches/bevy_ecs/fragmentation/mod.rs
Normal file
99
benches/benches/bevy_ecs/fragmentation/mod.rs
Normal file
|
@ -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<const X: usize = 0>(usize);
|
||||
#[derive(Component, Default)]
|
||||
#[component(storage = "SparseSet")]
|
||||
struct Sparse<const X: usize = 0>(usize);
|
||||
|
||||
fn flip_coin() -> bool {
|
||||
rand::random::<bool>()
|
||||
}
|
||||
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::<Table>(&mut world);
|
||||
let mut q: SystemState<Query<(Entity, &Table)>> =
|
||||
SystemState::<Query<(Entity, &Table<0>)>>::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::<Sparse>(&mut world);
|
||||
let mut q: SystemState<Query<(Entity, &Sparse)>> =
|
||||
SystemState::<Query<(Entity, &Sparse<0>)>>::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<T: Component + Default>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue