Wider ECS Benchmarks (#5123)

# Objective
As a part of evaluating #4800, at the behest of @cart, it was noted that the ECS microbenchmarks all focus on singular component queries, whereas in reality most systems will have wider queries with multiple components in each.

## Solution
Use const generics to add wider variants of existing benchmarks.
This commit is contained in:
James Liu 2022-06-29 02:29:50 +00:00
parent 6e50b249a4
commit ba3d8bedc5
10 changed files with 659 additions and 0 deletions

View File

@ -0,0 +1,70 @@
use bevy_ecs::prelude::*;
macro_rules! create_entities {
($world:ident; $( $variants:ident ),*) => {
$(
#[derive(Component)]
struct $variants(f32);
for _ in 0..20 {
$world.spawn().insert_bundle((
$variants(0.0),
Data::<0>(1.0),
Data::<1>(1.0),
Data::<2>(1.0),
Data::<3>(1.0),
Data::<4>(1.0),
Data::<5>(1.0),
Data::<6>(1.0),
Data::<7>(1.0),
Data::<8>(1.0),
Data::<9>(1.0),
Data::<10>(1.0),
));
}
)*
};
}
#[derive(Component)]
struct Data<const X: usize>(f32);
pub struct Benchmark<'w>(World, QueryState<(
&'w mut Data<0>,
&'w mut Data<1>,
&'w mut Data<2>,
&'w mut Data<3>,
&'w mut Data<4>,
&'w mut Data<5>,
&'w mut Data<6>,
&'w mut Data<7>,
&'w mut Data<8>,
&'w mut Data<9>,
&'w mut Data<10>,
)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
let query = world.query();
Self(world, query)
}
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut data| {
data.0.0 *= 2.0;
data.1.0 *= 2.0;
data.2.0 *= 2.0;
data.3.0 *= 2.0;
data.4.0 *= 2.0;
data.5.0 *= 2.0;
data.6.0 *= 2.0;
data.7.0 *= 2.0;
data.8.0 *= 2.0;
data.9.0 *= 2.0;
data.10.0 *= 2.0;
});
}
}

View File

@ -0,0 +1,70 @@
use bevy_ecs::prelude::*;
macro_rules! create_entities {
($world:ident; $( $variants:ident ),*) => {
$(
#[derive(Component)]
struct $variants(f32);
for _ in 0..20 {
$world.spawn().insert_bundle((
$variants(0.0),
Data::<0>(1.0),
Data::<1>(1.0),
Data::<2>(1.0),
Data::<3>(1.0),
Data::<4>(1.0),
Data::<5>(1.0),
Data::<6>(1.0),
Data::<7>(1.0),
Data::<8>(1.0),
Data::<9>(1.0),
Data::<10>(1.0),
));
}
)*
};
}
#[derive(Component)]
struct Data<const X: usize>(f32);
pub struct Benchmark<'w>(World, QueryState<(
&'w mut Data<0>,
&'w mut Data<1>,
&'w mut Data<2>,
&'w mut Data<3>,
&'w mut Data<4>,
&'w mut Data<5>,
&'w mut Data<6>,
&'w mut Data<7>,
&'w mut Data<8>,
&'w mut Data<9>,
&'w mut Data<10>,
)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
let query = world.query();
Self(world, query)
}
pub fn run(&mut self) {
for mut data in self.1.iter_mut(&mut self.0) {
data.0.0 *= 2.0;
data.1.0 *= 2.0;
data.2.0 *= 2.0;
data.3.0 *= 2.0;
data.4.0 *= 2.0;
data.5.0 *= 2.0;
data.6.0 *= 2.0;
data.7.0 *= 2.0;
data.8.0 *= 2.0;
data.9.0 *= 2.0;
data.10.0 *= 2.0;
}
}
}

View File

@ -5,7 +5,9 @@ mod add_remove_big_table;
mod add_remove_sparse_set;
mod add_remove_table;
mod frag_iter;
mod frag_iter_wide;
mod frag_iter_foreach;
mod frag_iter_foreach_wide;
mod get_component;
mod get_component_system;
mod heavy_compute;
@ -13,12 +15,18 @@ mod schedule;
mod simple_insert;
mod simple_insert_unbatched;
mod simple_iter;
mod simple_iter_wide;
mod simple_iter_foreach;
mod simple_iter_foreach_wide;
mod simple_iter_sparse;
mod simple_iter_sparse_wide;
mod simple_iter_sparse_foreach;
mod simple_iter_sparse_foreach_wide;
mod simple_iter_system;
mod sparse_frag_iter;
mod sparse_frag_iter_wide;
mod sparse_frag_iter_foreach;
mod sparse_frag_iter_foreach_wide;
fn bench_simple_insert(c: &mut Criterion) {
let mut group = c.benchmark_group("simple_insert");
@ -43,6 +51,10 @@ fn bench_simple_iter(c: &mut Criterion) {
let mut bench = simple_iter::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("wide", |b| {
let mut bench = simple_iter_wide::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("system", |b| {
let mut bench = simple_iter_system::Benchmark::new();
b.iter(move || bench.run());
@ -51,14 +63,26 @@ fn bench_simple_iter(c: &mut Criterion) {
let mut bench = simple_iter_sparse::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("sparse_wide", |b| {
let mut bench = simple_iter_sparse_wide::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("foreach", |b| {
let mut bench = simple_iter_foreach::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("foreach_wide", |b| {
let mut bench = simple_iter_foreach_wide::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("sparse_foreach", |b| {
let mut bench = simple_iter_sparse_foreach::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("sparse_foreach_wide", |b| {
let mut bench = simple_iter_sparse_foreach_wide::Benchmark::new();
b.iter(move || bench.run());
});
group.finish();
}
@ -70,10 +94,18 @@ fn bench_frag_iter_bc(c: &mut Criterion) {
let mut bench = frag_iter::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("wide", |b| {
let mut bench = frag_iter_wide::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("foreach", |b| {
let mut bench = frag_iter_foreach::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("foreach_wide", |b| {
let mut bench = frag_iter_foreach_wide::Benchmark::new();
b.iter(move || bench.run());
});
group.finish();
}
@ -85,10 +117,18 @@ fn bench_sparse_frag_iter(c: &mut Criterion) {
let mut bench = sparse_frag_iter::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("wide", |b| {
let mut bench = sparse_frag_iter_wide::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("foreach", |b| {
let mut bench = sparse_frag_iter_foreach::Benchmark::new();
b.iter(move || bench.run());
});
group.bench_function("foreach_wide", |b| {
let mut bench = sparse_frag_iter_foreach_wide::Benchmark::new();
b.iter(move || bench.run());
});
group.finish();
}

View File

@ -0,0 +1,63 @@
use bevy_ecs::prelude::*;
use glam::*;
#[derive(Component, Copy, Clone)]
struct Transform(Mat4);
#[derive(Component, Copy, Clone)]
struct Position<const X: usize>(Vec3);
#[derive(Component, Copy, Clone)]
struct Rotation(Vec3);
#[derive(Component, Copy, Clone)]
struct Velocity<const X: usize>(Vec3);
pub struct Benchmark<'w>(World, QueryState<(
&'w Velocity<0>,
&'w mut Position<0>,
&'w Velocity<1>,
&'w mut Position<1>,
&'w Velocity<2>,
&'w mut Position<2>,
&'w Velocity<3>,
&'w mut Position<3>,
&'w Velocity<4>,
&'w mut Position<4>,
)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
// TODO: batch this
for _ in 0..10_000 {
world.spawn().insert_bundle((
Transform(Mat4::from_scale(Vec3::ONE)),
Rotation(Vec3::X),
Position::<0>(Vec3::X),
Velocity::<0>(Vec3::X),
Position::<1>(Vec3::X),
Velocity::<1>(Vec3::X),
Position::<2>(Vec3::X),
Velocity::<2>(Vec3::X),
Position::<3>(Vec3::X),
Velocity::<3>(Vec3::X),
Position::<4>(Vec3::X),
Velocity::<4>(Vec3::X),
));
}
let query = world.query();
Self(world, query)
}
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut item| {
item.1.0 += item.0.0;
item.3.0 += item.2.0;
item.5.0 += item.4.0;
item.7.0 += item.6.0;
});
}
}

View File

@ -0,0 +1,65 @@
use bevy_ecs::prelude::*;
use glam::*;
#[derive(Component, Copy, Clone)]
struct Transform(Mat4);
#[derive(Component, Copy, Clone)]
#[component(storage = "SparseSet")]
struct Position<const X: usize>(Vec3);
#[derive(Component, Copy, Clone)]
struct Rotation(Vec3);
#[derive(Component, Copy, Clone)]
#[component(storage = "SparseSet")]
struct Velocity<const X: usize>(Vec3);
pub struct Benchmark<'w>(World, QueryState<(
&'w Velocity<0>,
&'w mut Position<0>,
&'w Velocity<1>,
&'w mut Position<1>,
&'w Velocity<2>,
&'w mut Position<2>,
&'w Velocity<3>,
&'w mut Position<3>,
&'w Velocity<4>,
&'w mut Position<4>,
)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
// TODO: batch this
for _ in 0..10_000 {
world.spawn().insert_bundle((
Transform(Mat4::from_scale(Vec3::ONE)),
Rotation(Vec3::X),
Position::<0>(Vec3::X),
Velocity::<0>(Vec3::X),
Position::<1>(Vec3::X),
Velocity::<1>(Vec3::X),
Position::<2>(Vec3::X),
Velocity::<2>(Vec3::X),
Position::<3>(Vec3::X),
Velocity::<3>(Vec3::X),
Position::<4>(Vec3::X),
Velocity::<4>(Vec3::X),
));
}
let query = world.query();
Self(world, query)
}
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut item| {
item.1.0 += item.0.0;
item.3.0 += item.2.0;
item.5.0 += item.4.0;
item.7.0 += item.6.0;
});
}
}

View File

@ -0,0 +1,65 @@
use bevy_ecs::prelude::*;
use glam::*;
#[derive(Component, Copy, Clone)]
struct Transform(Mat4);
#[derive(Component, Copy, Clone)]
#[component(storage = "SparseSet")]
struct Position<const X: usize>(Vec3);
#[derive(Component, Copy, Clone)]
struct Rotation(Vec3);
#[derive(Component, Copy, Clone)]
#[component(storage = "SparseSet")]
struct Velocity<const X: usize>(Vec3);
pub struct Benchmark<'w>(World, QueryState<(
&'w Velocity<0>,
&'w mut Position<0>,
&'w Velocity<1>,
&'w mut Position<1>,
&'w Velocity<2>,
&'w mut Position<2>,
&'w Velocity<3>,
&'w mut Position<3>,
&'w Velocity<4>,
&'w mut Position<4>,
)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
// TODO: batch this
for _ in 0..10_000 {
world.spawn().insert_bundle((
Transform(Mat4::from_scale(Vec3::ONE)),
Rotation(Vec3::X),
Position::<0>(Vec3::X),
Velocity::<0>(Vec3::X),
Position::<1>(Vec3::X),
Velocity::<1>(Vec3::X),
Position::<2>(Vec3::X),
Velocity::<2>(Vec3::X),
Position::<3>(Vec3::X),
Velocity::<3>(Vec3::X),
Position::<4>(Vec3::X),
Velocity::<4>(Vec3::X),
));
}
let query = world.query();
Self(world, query)
}
pub fn run(&mut self) {
for mut item in self.1.iter_mut(&mut self.0) {
item.1.0 += item.0.0;
item.3.0 += item.2.0;
item.5.0 += item.4.0;
item.7.0 += item.6.0;
}
}
}

View File

@ -0,0 +1,63 @@
use bevy_ecs::prelude::*;
use glam::*;
#[derive(Component, Copy, Clone)]
struct Transform(Mat4);
#[derive(Component, Copy, Clone)]
struct Position<const X: usize>(Vec3);
#[derive(Component, Copy, Clone)]
struct Rotation(Vec3);
#[derive(Component, Copy, Clone)]
struct Velocity<const X: usize>(Vec3);
pub struct Benchmark<'w>(World, QueryState<(
&'w Velocity<0>,
&'w mut Position<0>,
&'w Velocity<1>,
&'w mut Position<1>,
&'w Velocity<2>,
&'w mut Position<2>,
&'w Velocity<3>,
&'w mut Position<3>,
&'w Velocity<4>,
&'w mut Position<4>,
)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
// TODO: batch this
for _ in 0..10_000 {
world.spawn().insert_bundle((
Transform(Mat4::from_scale(Vec3::ONE)),
Rotation(Vec3::X),
Position::<0>(Vec3::X),
Velocity::<0>(Vec3::X),
Position::<1>(Vec3::X),
Velocity::<1>(Vec3::X),
Position::<2>(Vec3::X),
Velocity::<2>(Vec3::X),
Position::<3>(Vec3::X),
Velocity::<3>(Vec3::X),
Position::<4>(Vec3::X),
Velocity::<4>(Vec3::X),
));
}
let query = world.query();
Self(world, query)
}
pub fn run(&mut self) {
for mut item in self.1.iter_mut(&mut self.0) {
item.1.0 += item.0.0;
item.3.0 += item.2.0;
item.5.0 += item.4.0;
item.7.0 += item.6.0;
}
}
}

View File

@ -0,0 +1,80 @@
use bevy_ecs::prelude::*;
macro_rules! create_entities {
($world:ident; $( $variants:ident ),*) => {
$(
#[derive(Component)]
struct $variants(f32);
for _ in 0..5 {
$world.spawn().insert($variants(0.0));
}
)*
};
}
#[derive(Component)]
struct Data<const X: usize>(f32);
pub struct Benchmark<'w>(World, QueryState<(
&'w mut Data<0>,
&'w mut Data<1>,
&'w mut Data<2>,
&'w mut Data<3>,
&'w mut Data<4>,
&'w mut Data<5>,
&'w mut Data<6>,
&'w mut Data<7>,
&'w mut Data<8>,
&'w mut Data<9>,
&'w mut Data<10>,
)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
for _ in 0..5 {
world.spawn().insert_bundle((
Data::<0>(1.0),
Data::<1>(1.0),
Data::<2>(1.0),
Data::<3>(1.0),
Data::<4>(1.0),
Data::<5>(1.0),
Data::<6>(1.0),
Data::<7>(1.0),
Data::<8>(1.0),
Data::<9>(1.0),
Data::<10>(1.0),
));
}
create_entities!(world; C00, C01, C02, C03, C04, C05, C06, C07, C08, C09);
create_entities!(world; C10, C11, C12, C13, C14, C15, C16, C17, C18, C19);
create_entities!(world; C20, C21, C22, C23, C24, C25, C26, C27, C28, C29);
create_entities!(world; C30, C31, C32, C33, C34, C35, C36, C37, C38, C39);
create_entities!(world; C40, C41, C42, C43, C44, C45, C46, C47, C48, C49);
create_entities!(world; C50, C51, C52, C53, C54, C55, C56, C57, C58, C59);
create_entities!(world; C60, C61, C62, C63, C64, C65, C66, C67, C68, C69);
create_entities!(world; C70, C71, C72, C73, C74, C75, C76, C77, C78, C79);
create_entities!(world; C80, C81, C82, C83, C84, C85, C86, C87, C88, C89);
create_entities!(world; C90, C91, C92, C93, C94, C95, C96, C97, C98, C99);
let query = world.query();
Self(world, query)
}
pub fn run(&mut self) {
self.1.for_each_mut(&mut self.0, |mut data| {
data.0.0 *= 2.0;
data.1.0 *= 2.0;
data.2.0 *= 2.0;
data.3.0 *= 2.0;
data.4.0 *= 2.0;
data.5.0 *= 2.0;
data.6.0 *= 2.0;
data.7.0 *= 2.0;
data.8.0 *= 2.0;
data.9.0 *= 2.0;
data.10.0 *= 2.0;
});
}
}

View File

@ -0,0 +1,80 @@
use bevy_ecs::prelude::*;
macro_rules! create_entities {
($world:ident; $( $variants:ident ),*) => {
$(
#[derive(Component)]
struct $variants(f32);
for _ in 0..5 {
$world.spawn().insert($variants(0.0));
}
)*
};
}
#[derive(Component)]
struct Data<const X: usize>(f32);
pub struct Benchmark<'w>(World, QueryState<(
&'w mut Data<0>,
&'w mut Data<1>,
&'w mut Data<2>,
&'w mut Data<3>,
&'w mut Data<4>,
&'w mut Data<5>,
&'w mut Data<6>,
&'w mut Data<7>,
&'w mut Data<8>,
&'w mut Data<9>,
&'w mut Data<10>,
)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
for _ in 0..5 {
world.spawn().insert_bundle((
Data::<0>(1.0),
Data::<1>(1.0),
Data::<2>(1.0),
Data::<3>(1.0),
Data::<4>(1.0),
Data::<5>(1.0),
Data::<6>(1.0),
Data::<7>(1.0),
Data::<8>(1.0),
Data::<9>(1.0),
Data::<10>(1.0),
));
}
create_entities!(world; C00, C01, C02, C03, C04, C05, C06, C07, C08, C09);
create_entities!(world; C10, C11, C12, C13, C14, C15, C16, C17, C18, C19);
create_entities!(world; C20, C21, C22, C23, C24, C25, C26, C27, C28, C29);
create_entities!(world; C30, C31, C32, C33, C34, C35, C36, C37, C38, C39);
create_entities!(world; C40, C41, C42, C43, C44, C45, C46, C47, C48, C49);
create_entities!(world; C50, C51, C52, C53, C54, C55, C56, C57, C58, C59);
create_entities!(world; C60, C61, C62, C63, C64, C65, C66, C67, C68, C69);
create_entities!(world; C70, C71, C72, C73, C74, C75, C76, C77, C78, C79);
create_entities!(world; C80, C81, C82, C83, C84, C85, C86, C87, C88, C89);
create_entities!(world; C90, C91, C92, C93, C94, C95, C96, C97, C98, C99);
let query = world.query();
Self(world, query)
}
pub fn run(&mut self) {
for mut data in self.1.iter_mut(&mut self.0) {
data.0.0 *= 2.0;
data.1.0 *= 2.0;
data.2.0 *= 2.0;
data.3.0 *= 2.0;
data.4.0 *= 2.0;
data.5.0 *= 2.0;
data.6.0 *= 2.0;
data.7.0 *= 2.0;
data.8.0 *= 2.0;
data.9.0 *= 2.0;
data.10.0 *= 2.0;
}
}
}

View File

@ -2,6 +2,7 @@ use bevy_ecs::{
component::Component,
entity::Entity,
system::{Query, SystemState},
bundle::Bundle,
world::World,
};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
@ -26,6 +27,12 @@ struct Table(f32);
#[derive(Component, Default)]
#[component(storage = "SparseSet")]
struct Sparse(f32);
#[derive(Component, Default)]
#[component(storage = "Table")]
struct WideTable<const X: usize>(f32);
#[derive(Component, Default)]
#[component(storage = "SparseSet")]
struct WideSparse<const X: usize>(f32);
const RANGE: std::ops::Range<u32> = 5..6;
@ -39,6 +46,12 @@ fn setup<T: Component + Default>(entity_count: u32) -> World {
black_box(world)
}
fn setup_wide<T: Bundle + Default>(entity_count: u32) -> World {
let mut world = World::default();
world.spawn_batch((0..entity_count).map(|_| T::default()));
black_box(world)
}
fn world_entity(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("world_entity");
group.warm_up_time(std::time::Duration::from_millis(500));
@ -108,10 +121,60 @@ fn world_query_get(criterion: &mut Criterion) {
}
});
});
group.bench_function(format!("{}_entities_table_wide", entity_count), |bencher| {
let mut world = setup_wide::<(
WideTable<0>,
WideTable<1>,
WideTable<2>,
WideTable<3>,
WideTable<4>,
WideTable<5>,
)>(entity_count);
let mut query = world.query::<(
&WideTable<0>,
&WideTable<1>,
&WideTable<2>,
&WideTable<3>,
&WideTable<4>,
&WideTable<5>,
)>();
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
assert!(query.get(&world, entity).is_ok());
}
});
});
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
let mut world = setup::<Sparse>(entity_count);
let mut query = world.query::<&Sparse>();
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
assert!(query.get(&world, entity).is_ok());
}
});
});
group.bench_function(format!("{}_entities_sparse_wide", entity_count), |bencher| {
let mut world = setup_wide::<(
WideSparse<0>,
WideSparse<1>,
WideSparse<2>,
WideSparse<3>,
WideSparse<4>,
WideSparse<5>,
)>(entity_count);
let mut query = world.query::<(
&WideSparse<0>,
&WideSparse<1>,
&WideSparse<2>,
&WideSparse<3>,
&WideSparse<4>,
&WideSparse<5>,
)>();
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);