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 components;
|
||||||
mod events;
|
mod events;
|
||||||
|
mod fragmentation;
|
||||||
mod iteration;
|
mod iteration;
|
||||||
mod observers;
|
mod observers;
|
||||||
mod scheduling;
|
mod scheduling;
|
||||||
|
@ -11,6 +12,7 @@ criterion_main!(
|
||||||
components::components_benches,
|
components::components_benches,
|
||||||
events::event_benches,
|
events::event_benches,
|
||||||
iteration::iterations_benches,
|
iteration::iterations_benches,
|
||||||
|
fragmentation::fragmentation_benches,
|
||||||
observers::observer_benches,
|
observers::observer_benches,
|
||||||
scheduling::scheduling_benches,
|
scheduling::scheduling_benches,
|
||||||
world::world_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
|
where
|
||||||
Func: FnMut(B, D::Item<'w>) -> B,
|
Func: FnMut(B, D::Item<'w>) -> B,
|
||||||
{
|
{
|
||||||
|
if table.is_empty() {
|
||||||
|
return accum;
|
||||||
|
}
|
||||||
assert!(
|
assert!(
|
||||||
rows.end <= u32::MAX as usize,
|
rows.end <= u32::MAX as usize,
|
||||||
"TableRow is only valid up to u32::MAX"
|
"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
|
where
|
||||||
Func: FnMut(B, D::Item<'w>) -> B,
|
Func: FnMut(B, D::Item<'w>) -> B,
|
||||||
{
|
{
|
||||||
|
if archetype.is_empty() {
|
||||||
|
return accum;
|
||||||
|
}
|
||||||
let table = self.tables.get(archetype.table_id()).debug_checked_unwrap();
|
let table = self.tables.get(archetype.table_id()).debug_checked_unwrap();
|
||||||
D::set_archetype(
|
D::set_archetype(
|
||||||
&mut self.cursor.fetch,
|
&mut self.cursor.fetch,
|
||||||
|
@ -186,6 +192,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
|
||||||
where
|
where
|
||||||
Func: FnMut(B, D::Item<'w>) -> B,
|
Func: FnMut(B, D::Item<'w>) -> B,
|
||||||
{
|
{
|
||||||
|
if archetype.is_empty() {
|
||||||
|
return accum;
|
||||||
|
}
|
||||||
assert!(
|
assert!(
|
||||||
rows.end <= u32::MAX as usize,
|
rows.end <= u32::MAX as usize,
|
||||||
"TableRow is only valid up to u32::MAX"
|
"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 {
|
if self.current_row == self.current_len {
|
||||||
let table_id = self.storage_id_iter.next()?.table_id;
|
let table_id = self.storage_id_iter.next()?.table_id;
|
||||||
let table = tables.get(table_id).debug_checked_unwrap();
|
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,
|
// 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
|
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -1725,7 +1737,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
|
||||||
self.table_entities = table.entities();
|
self.table_entities = table.entities();
|
||||||
self.current_len = table.entity_count();
|
self.current_len = table.entity_count();
|
||||||
self.current_row = 0;
|
self.current_row = 0;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: set_table was called prior.
|
// 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 {
|
if self.current_row == self.current_len {
|
||||||
let archetype_id = self.storage_id_iter.next()?.archetype_id;
|
let archetype_id = self.storage_id_iter.next()?.archetype_id;
|
||||||
let archetype = archetypes.get(archetype_id).debug_checked_unwrap();
|
let archetype = archetypes.get(archetype_id).debug_checked_unwrap();
|
||||||
|
if archetype.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let table = tables.get(archetype.table_id()).debug_checked_unwrap();
|
let table = tables.get(archetype.table_id()).debug_checked_unwrap();
|
||||||
// SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for,
|
// 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
|
// `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.archetype_entities = archetype.entities();
|
||||||
self.current_len = archetype.len();
|
self.current_len = archetype.len();
|
||||||
self.current_row = 0;
|
self.current_row = 0;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: set_archetype was called prior.
|
// SAFETY: set_archetype was called prior.
|
||||||
|
|
Loading…
Reference in a new issue