Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Skip empty archetypes and tables when iterating over queries #4724

Closed
wants to merge 15 commits into from
Closed
5 changes: 5 additions & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ name = "archetype_updates"
path = "benches/bevy_ecs/archetype_updates.rs"
harness = false

[[bench]]
name = "empty_archetypes"
path = "benches/bevy_ecs/empty_archetypes.rs"
harness = false

[[bench]]
name = "ecs_bench_suite"
path = "benches/bevy_ecs/ecs_bench_suite/mod.rs"
Expand Down
253 changes: 253 additions & 0 deletions benches/benches/bevy_ecs/empty_archetypes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
use bevy_ecs::{
component::Component,
prelude::*,
schedule::{Stage, SystemStage},
world::World,
};
use bevy_tasks::{ComputeTaskPool, TaskPool};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};

criterion_group!(benches, empty_archetypes);
criterion_main!(benches);

#[derive(Component)]
struct A<const N: u16>(f32);

fn iter(
query: Query<(
&A<0>,
&A<1>,
&A<2>,
&A<3>,
&A<4>,
&A<5>,
&A<6>,
&A<7>,
&A<8>,
&A<9>,
&A<10>,
&A<11>,
&A<12>,
)>,
) {
for comp in query.iter() {
black_box(comp);
}
}

fn for_each(
query: Query<(
&A<0>,
&A<1>,
&A<2>,
&A<3>,
&A<4>,
&A<5>,
&A<6>,
&A<7>,
&A<8>,
&A<9>,
&A<10>,
&A<11>,
&A<12>,
)>,
) {
query.for_each(|comp| {
black_box(comp);
});
}

fn par_for_each(
task_pool: Res<ComputeTaskPool>,
query: Query<(
&A<0>,
&A<1>,
&A<2>,
&A<3>,
&A<4>,
&A<5>,
&A<6>,
&A<7>,
&A<8>,
&A<9>,
&A<10>,
&A<11>,
&A<12>,
)>,
) {
query.par_for_each(&*task_pool, 64, |comp| {
black_box(comp);
});
}

fn setup(parallel: bool, setup: impl FnOnce(&mut SystemStage)) -> (World, SystemStage) {
let mut world = World::new();
let mut stage = SystemStage::parallel();
if parallel {
world.insert_resource(ComputeTaskPool(TaskPool::default()));
}
setup(&mut stage);
(world, stage)
}

/// create `count` entities with distinct archetypes
fn add_archetypes(world: &mut World, count: u16) {
for i in 0..count {
let mut e = world.spawn();
e.insert(A::<0>(1.0));
e.insert(A::<1>(1.0));
e.insert(A::<2>(1.0));
e.insert(A::<3>(1.0));
e.insert(A::<4>(1.0));
e.insert(A::<5>(1.0));
e.insert(A::<6>(1.0));
e.insert(A::<7>(1.0));
e.insert(A::<8>(1.0));
e.insert(A::<9>(1.0));
e.insert(A::<10>(1.0));
e.insert(A::<11>(1.0));
e.insert(A::<12>(1.0));
if i & 1 << 1 != 0 {
e.insert(A::<13>(1.0));
}
if i & 1 << 2 != 0 {
e.insert(A::<14>(1.0));
}
if i & 1 << 3 != 0 {
e.insert(A::<15>(1.0));
}
if i & 1 << 4 != 0 {
e.insert(A::<16>(1.0));
}
if i & 1 << 5 != 0 {
e.insert(A::<18>(1.0));
}
if i & 1 << 6 != 0 {
e.insert(A::<19>(1.0));
}
if i & 1 << 7 != 0 {
e.insert(A::<20>(1.0));
}
if i & 1 << 8 != 0 {
e.insert(A::<21>(1.0));
}
if i & 1 << 9 != 0 {
e.insert(A::<22>(1.0));
}
if i & 1 << 10 != 0 {
e.insert(A::<23>(1.0));
}
if i & 1 << 11 != 0 {
e.insert(A::<24>(1.0));
}
if i & 1 << 12 != 0 {
e.insert(A::<25>(1.0));
}
if i & 1 << 13 != 0 {
e.insert(A::<26>(1.0));
}
if i & 1 << 14 != 0 {
e.insert(A::<27>(1.0));
}
if i & 1 << 15 != 0 {
e.insert(A::<28>(1.0));
}
}
}

fn empty_archetypes(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("empty_archetypes");
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut stage) = setup(true, |stage| {
stage.add_system(iter);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
let mut e = world.spawn();
e.insert(A::<0>(1.0));
e.insert(A::<1>(1.0));
e.insert(A::<2>(1.0));
e.insert(A::<3>(1.0));
e.insert(A::<4>(1.0));
e.insert(A::<5>(1.0));
e.insert(A::<6>(1.0));
e.insert(A::<7>(1.0));
e.insert(A::<8>(1.0));
e.insert(A::<9>(1.0));
e.insert(A::<10>(1.0));
e.insert(A::<11>(1.0));
e.insert(A::<12>(1.0));
stage.run(&mut world);
group.bench_with_input(
BenchmarkId::new("iter", archetype_count),
&archetype_count,
|bencher, &_| {
bencher.iter(|| {
stage.run(&mut world);
})
},
);
}
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut stage) = setup(true, |stage| {
stage.add_system(for_each);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
let mut e = world.spawn();
e.insert(A::<0>(1.0));
e.insert(A::<1>(1.0));
e.insert(A::<2>(1.0));
e.insert(A::<3>(1.0));
e.insert(A::<4>(1.0));
e.insert(A::<5>(1.0));
e.insert(A::<6>(1.0));
e.insert(A::<7>(1.0));
e.insert(A::<8>(1.0));
e.insert(A::<9>(1.0));
e.insert(A::<10>(1.0));
e.insert(A::<11>(1.0));
e.insert(A::<12>(1.0));
stage.run(&mut world);
group.bench_with_input(
BenchmarkId::new("for_each", archetype_count),
&archetype_count,
|bencher, &_| {
bencher.iter(|| {
stage.run(&mut world);
})
},
);
}
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut stage) = setup(true, |stage| {
stage.add_system(par_for_each);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
let mut e = world.spawn();
e.insert(A::<0>(1.0));
e.insert(A::<1>(1.0));
e.insert(A::<2>(1.0));
e.insert(A::<3>(1.0));
e.insert(A::<4>(1.0));
e.insert(A::<5>(1.0));
e.insert(A::<6>(1.0));
e.insert(A::<7>(1.0));
e.insert(A::<8>(1.0));
e.insert(A::<9>(1.0));
e.insert(A::<10>(1.0));
e.insert(A::<11>(1.0));
e.insert(A::<12>(1.0));
stage.run(&mut world);
group.bench_with_input(
BenchmarkId::new("par_for_each", archetype_count),
&archetype_count,
|bencher, &_| {
bencher.iter(|| {
stage.run(&mut world);
})
},
);
}
}
20 changes: 16 additions & 4 deletions crates/bevy_ecs/src/query/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,14 @@ where
if self.current_index == self.current_len {
let table_id = self.table_id_iter.next()?;
let table = &self.tables[*table_id];
if table.is_empty() {
continue;
}

self.fetch.set_table(&self.query_state.fetch_state, table);
self.filter.set_table(&self.query_state.filter_state, table);
self.current_len = table.len();
self.current_index = 0;
continue;
}

if !self.filter.table_filter_fetch(self.current_index) {
Expand All @@ -107,6 +110,10 @@ where
if self.current_index == self.current_len {
let archetype_id = self.archetype_id_iter.next()?;
let archetype = &self.archetypes[*archetype_id];
if archetype.is_empty() {
continue;
}

self.fetch.set_archetype(
&self.query_state.fetch_state,
archetype,
Expand All @@ -119,7 +126,6 @@ where
);
self.current_len = archetype.len();
self.current_index = 0;
continue;
}

if !self.filter.archetype_filter_fetch(self.current_index) {
Expand Down Expand Up @@ -434,11 +440,14 @@ where
if self.current_index == self.current_len {
let table_id = self.table_id_iter.next()?;
let table = &tables[*table_id];
if table.is_empty() {
continue;
}

self.fetch.set_table(&query_state.fetch_state, table);
self.filter.set_table(&query_state.filter_state, table);
self.current_len = table.len();
self.current_index = 0;
continue;
}

if !self.filter.table_filter_fetch(self.current_index) {
Expand All @@ -456,13 +465,16 @@ where
if self.current_index == self.current_len {
let archetype_id = self.archetype_id_iter.next()?;
let archetype = &archetypes[*archetype_id];
if archetype.is_empty() {
continue;
}

self.fetch
.set_archetype(&query_state.fetch_state, archetype, tables);
self.filter
.set_archetype(&query_state.filter_state, archetype, tables);
self.current_len = archetype.len();
self.current_index = 0;
continue;
}

if !self.filter.archetype_filter_fetch(self.current_index) {
Expand Down
16 changes: 16 additions & 0 deletions crates/bevy_ecs/src/query/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,10 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
let tables = &world.storages().tables;
for table_id in &self.matched_table_ids {
let table = &tables[*table_id];
if table.is_empty() {
continue;
}

fetch.set_table(&self.fetch_state, table);
filter.set_table(&self.filter_state, table);

Expand All @@ -812,6 +816,10 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
let tables = &world.storages().tables;
for archetype_id in &self.matched_archetype_ids {
let archetype = &archetypes[*archetype_id];
if archetype.is_empty() {
continue;
}

fetch.set_archetype(&self.fetch_state, archetype, tables);
filter.set_archetype(&self.filter_state, archetype, tables);

Expand Down Expand Up @@ -855,6 +863,10 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
let tables = &world.storages().tables;
for table_id in &self.matched_table_ids {
let table = &tables[*table_id];
if table.is_empty() {
continue;
}

let mut offset = 0;
while offset < table.len() {
let func = func.clone();
Expand Down Expand Up @@ -898,6 +910,10 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
for archetype_id in &self.matched_archetype_ids {
let mut offset = 0;
let archetype = &archetypes[*archetype_id];
if archetype.is_empty() {
continue;
}

while offset < archetype.len() {
let func = func.clone();
let len = batch_size.min(archetype.len() - offset);
Expand Down