mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
Recalibrated observe benchmark (#14381)
# Objective - The event propagation benchmark is largely derived from bevy_eventlistener. However, it doesn't accurately reflect performance of bevy side, as our event bubble propagation is based on observer. ## Solution - added several new benchmarks that focuse on observer itself rather than event bubble
This commit is contained in:
parent
dcbd30200e
commit
e5bf59d712
4 changed files with 126 additions and 88 deletions
|
@ -1,6 +1,8 @@
|
||||||
use criterion::criterion_group;
|
use criterion::criterion_group;
|
||||||
|
|
||||||
mod propagation;
|
mod propagation;
|
||||||
|
mod simple;
|
||||||
use propagation::*;
|
use propagation::*;
|
||||||
|
use simple::*;
|
||||||
|
|
||||||
criterion_group!(observer_benches, event_propagation);
|
criterion_group!(observer_benches, event_propagation, observe_simple);
|
||||||
|
|
|
@ -1,99 +1,66 @@
|
||||||
use bevy_app::{App, First, Startup};
|
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{Event, EventWriter},
|
event::{Event, EventWriter},
|
||||||
observer::Trigger,
|
observer::Trigger,
|
||||||
query::{Or, With, Without},
|
world::World,
|
||||||
system::{Commands, EntityCommands, Query},
|
|
||||||
};
|
};
|
||||||
use bevy_hierarchy::{BuildChildren, Children, Parent};
|
use bevy_hierarchy::{BuildChildren, Children, Parent};
|
||||||
use bevy_internal::MinimalPlugins;
|
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
use rand::{prelude::SliceRandom, SeedableRng};
|
||||||
use rand::{seq::IteratorRandom, Rng};
|
use rand::{seq::IteratorRandom, Rng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
|
||||||
const DENSITY: usize = 20; // percent of nodes with listeners
|
const DENSITY: usize = 20; // percent of nodes with listeners
|
||||||
const ENTITY_DEPTH: usize = 64;
|
const ENTITY_DEPTH: usize = 64;
|
||||||
const ENTITY_WIDTH: usize = 200;
|
const ENTITY_WIDTH: usize = 200;
|
||||||
const N_EVENTS: usize = 500;
|
const N_EVENTS: usize = 500;
|
||||||
|
fn deterministic_rand() -> ChaCha8Rng {
|
||||||
|
ChaCha8Rng::seed_from_u64(42)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn event_propagation(criterion: &mut Criterion) {
|
pub fn event_propagation(criterion: &mut Criterion) {
|
||||||
let mut group = criterion.benchmark_group("event_propagation");
|
let mut group = criterion.benchmark_group("event_propagation");
|
||||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||||
group.measurement_time(std::time::Duration::from_secs(4));
|
group.measurement_time(std::time::Duration::from_secs(4));
|
||||||
|
|
||||||
group.bench_function("baseline", |bencher| {
|
|
||||||
let mut app = App::new();
|
|
||||||
app.add_plugins(MinimalPlugins)
|
|
||||||
.add_systems(Startup, spawn_listener_hierarchy);
|
|
||||||
app.update();
|
|
||||||
|
|
||||||
bencher.iter(|| {
|
|
||||||
black_box(app.update());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("single_event_type", |bencher| {
|
group.bench_function("single_event_type", |bencher| {
|
||||||
let mut app = App::new();
|
let mut world = World::new();
|
||||||
app.add_plugins(MinimalPlugins)
|
let (roots, leaves, nodes) = spawn_listener_hierarchy(&mut world);
|
||||||
.add_systems(
|
add_listeners_to_hierarchy::<DENSITY, 1>(&roots, &leaves, &nodes, &mut world);
|
||||||
Startup,
|
|
||||||
(
|
|
||||||
spawn_listener_hierarchy,
|
|
||||||
add_listeners_to_hierarchy::<DENSITY, 1>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.add_systems(First, send_events::<1, N_EVENTS>);
|
|
||||||
app.update();
|
|
||||||
|
|
||||||
bencher.iter(|| {
|
bencher.iter(|| {
|
||||||
black_box(app.update());
|
send_events::<1, N_EVENTS>(&mut world, &leaves);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group.bench_function("single_event_type_no_listeners", |bencher| {
|
group.bench_function("single_event_type_no_listeners", |bencher| {
|
||||||
let mut app = App::new();
|
let mut world = World::new();
|
||||||
app.add_plugins(MinimalPlugins)
|
let (roots, leaves, nodes) = spawn_listener_hierarchy(&mut world);
|
||||||
.add_systems(
|
add_listeners_to_hierarchy::<DENSITY, 1>(&roots, &leaves, &nodes, &mut world);
|
||||||
Startup,
|
|
||||||
(
|
|
||||||
spawn_listener_hierarchy,
|
|
||||||
add_listeners_to_hierarchy::<DENSITY, 1>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.add_systems(First, send_events::<9, N_EVENTS>);
|
|
||||||
app.update();
|
|
||||||
|
|
||||||
bencher.iter(|| {
|
bencher.iter(|| {
|
||||||
black_box(app.update());
|
// no listeners to observe TestEvent<9>
|
||||||
|
send_events::<9, N_EVENTS>(&mut world, &leaves);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group.bench_function("four_event_types", |bencher| {
|
group.bench_function("four_event_types", |bencher| {
|
||||||
let mut app = App::new();
|
let mut world = World::new();
|
||||||
|
let (roots, leaves, nodes) = spawn_listener_hierarchy(&mut world);
|
||||||
const FRAC_N_EVENTS_4: usize = N_EVENTS / 4;
|
const FRAC_N_EVENTS_4: usize = N_EVENTS / 4;
|
||||||
const FRAC_DENSITY_4: usize = DENSITY / 4;
|
const FRAC_DENSITY_4: usize = DENSITY / 4;
|
||||||
|
add_listeners_to_hierarchy::<FRAC_DENSITY_4, 1>(&roots, &leaves, &nodes, &mut world);
|
||||||
app.add_plugins(MinimalPlugins)
|
add_listeners_to_hierarchy::<FRAC_DENSITY_4, 2>(&roots, &leaves, &nodes, &mut world);
|
||||||
.add_systems(
|
add_listeners_to_hierarchy::<FRAC_DENSITY_4, 3>(&roots, &leaves, &nodes, &mut world);
|
||||||
Startup,
|
add_listeners_to_hierarchy::<FRAC_DENSITY_4, 4>(&roots, &leaves, &nodes, &mut world);
|
||||||
(
|
|
||||||
spawn_listener_hierarchy,
|
|
||||||
add_listeners_to_hierarchy::<FRAC_DENSITY_4, 1>,
|
|
||||||
add_listeners_to_hierarchy::<FRAC_DENSITY_4, 2>,
|
|
||||||
add_listeners_to_hierarchy::<FRAC_DENSITY_4, 3>,
|
|
||||||
add_listeners_to_hierarchy::<FRAC_DENSITY_4, 4>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.add_systems(First, send_events::<1, FRAC_N_EVENTS_4>)
|
|
||||||
.add_systems(First, send_events::<2, FRAC_N_EVENTS_4>)
|
|
||||||
.add_systems(First, send_events::<3, FRAC_N_EVENTS_4>)
|
|
||||||
.add_systems(First, send_events::<4, FRAC_N_EVENTS_4>);
|
|
||||||
app.update();
|
|
||||||
|
|
||||||
bencher.iter(|| {
|
bencher.iter(|| {
|
||||||
black_box(app.update());
|
send_events::<1, FRAC_N_EVENTS_4>(&mut world, &leaves);
|
||||||
|
send_events::<2, FRAC_N_EVENTS_4>(&mut world, &leaves);
|
||||||
|
send_events::<3, FRAC_N_EVENTS_4>(&mut world, &leaves);
|
||||||
|
send_events::<4, FRAC_N_EVENTS_4>(&mut world, &leaves);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -108,44 +75,54 @@ impl<const N: usize> Event for TestEvent<N> {
|
||||||
const AUTO_PROPAGATE: bool = true;
|
const AUTO_PROPAGATE: bool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_events<const N: usize, const N_EVENTS: usize>(
|
fn send_events<const N: usize, const N_EVENTS: usize>(world: &mut World, leaves: &Vec<Entity>) {
|
||||||
mut commands: Commands,
|
let target = leaves.iter().choose(&mut rand::thread_rng()).unwrap();
|
||||||
entities: Query<Entity, Without<Children>>,
|
|
||||||
) {
|
|
||||||
let target = entities.iter().choose(&mut rand::thread_rng()).unwrap();
|
|
||||||
(0..N_EVENTS).for_each(|_| {
|
(0..N_EVENTS).for_each(|_| {
|
||||||
commands.trigger_targets(TestEvent::<N> {}, target);
|
world.trigger_targets(TestEvent::<N> {}, *target);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_listener_hierarchy(mut commands: Commands) {
|
fn spawn_listener_hierarchy(world: &mut World) -> (Vec<Entity>, Vec<Entity>, Vec<Entity>) {
|
||||||
|
let mut roots = vec![];
|
||||||
|
let mut leaves = vec![];
|
||||||
|
let mut nodes = vec![];
|
||||||
for _ in 0..ENTITY_WIDTH {
|
for _ in 0..ENTITY_WIDTH {
|
||||||
let mut parent = commands.spawn_empty().id();
|
let mut parent = world.spawn_empty().id();
|
||||||
|
roots.push(parent);
|
||||||
for _ in 0..ENTITY_DEPTH {
|
for _ in 0..ENTITY_DEPTH {
|
||||||
let child = commands.spawn_empty().id();
|
let child = world.spawn_empty().id();
|
||||||
commands.entity(parent).add_child(child);
|
nodes.push(child);
|
||||||
|
|
||||||
|
world.entity_mut(parent).add_child(child);
|
||||||
parent = child;
|
parent = child;
|
||||||
}
|
}
|
||||||
|
nodes.pop();
|
||||||
|
leaves.push(parent);
|
||||||
}
|
}
|
||||||
|
(roots, leaves, nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty_listener<const N: usize>(_trigger: Trigger<TestEvent<N>>) {}
|
|
||||||
|
|
||||||
fn add_listeners_to_hierarchy<const DENSITY: usize, const N: usize>(
|
fn add_listeners_to_hierarchy<const DENSITY: usize, const N: usize>(
|
||||||
mut commands: Commands,
|
roots: &Vec<Entity>,
|
||||||
roots_and_leaves: Query<Entity, Or<(Without<Parent>, Without<Children>)>>,
|
leaves: &Vec<Entity>,
|
||||||
nodes: Query<Entity, (With<Parent>, With<Children>)>,
|
nodes: &Vec<Entity>,
|
||||||
|
world: &mut World,
|
||||||
) {
|
) {
|
||||||
for entity in &roots_and_leaves {
|
for e in roots.iter() {
|
||||||
commands.entity(entity).observe(empty_listener::<N>);
|
world.entity_mut(*e).observe(empty_listener::<N>);
|
||||||
|
}
|
||||||
|
for e in leaves.iter() {
|
||||||
|
world.entity_mut(*e).observe(empty_listener::<N>);
|
||||||
|
}
|
||||||
|
let mut rng = deterministic_rand();
|
||||||
|
for e in nodes.iter() {
|
||||||
|
if rng.gen_bool(DENSITY as f64 / 100.0) {
|
||||||
|
world.entity_mut(*e).observe(empty_listener::<N>);
|
||||||
}
|
}
|
||||||
for entity in &nodes {
|
|
||||||
maybe_insert_listener::<DENSITY, N>(&mut commands.entity(entity));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_insert_listener<const DENSITY: usize, const N: usize>(commands: &mut EntityCommands) {
|
fn empty_listener<const N: usize>(trigger: Trigger<TestEvent<N>>) {
|
||||||
if rand::thread_rng().gen_bool(DENSITY as f64 / 100.0) {
|
black_box(trigger);
|
||||||
commands.observe(empty_listener::<N>);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
49
benches/benches/bevy_ecs/observers/simple.rs
Normal file
49
benches/benches/bevy_ecs/observers/simple.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use bevy_ecs::{entity::Entity, event::Event, observer::Trigger, world::World};
|
||||||
|
|
||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
use rand::{prelude::SliceRandom, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
fn deterministic_rand() -> ChaCha8Rng {
|
||||||
|
ChaCha8Rng::seed_from_u64(42)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Event)]
|
||||||
|
struct EventBase;
|
||||||
|
|
||||||
|
pub fn observe_simple(criterion: &mut Criterion) {
|
||||||
|
let mut group = criterion.benchmark_group("observe");
|
||||||
|
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||||
|
group.measurement_time(std::time::Duration::from_secs(4));
|
||||||
|
|
||||||
|
group.bench_function("trigger_simple", |bencher| {
|
||||||
|
let mut world = World::new();
|
||||||
|
world.observe(empty_listener_base);
|
||||||
|
bencher.iter(|| {
|
||||||
|
for _ in 0..10000 {
|
||||||
|
world.trigger(EventBase)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.bench_function("trigger_targets_simple/10000_entity", |bencher| {
|
||||||
|
let mut world = World::new();
|
||||||
|
let mut entities = vec![];
|
||||||
|
for _ in 0..10000 {
|
||||||
|
entities.push(world.spawn_empty().observe(empty_listener_base).id());
|
||||||
|
}
|
||||||
|
entities.shuffle(&mut deterministic_rand());
|
||||||
|
bencher.iter(|| {
|
||||||
|
send_base_event(&mut world, &entities);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_listener_base(trigger: Trigger<EventBase>) {
|
||||||
|
black_box(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_base_event(world: &mut World, entities: &Vec<Entity>) {
|
||||||
|
world.trigger_targets(EventBase, entities);
|
||||||
|
}
|
|
@ -175,3 +175,13 @@ impl<const N: usize> TriggerTargets for [ComponentId; N] {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TriggerTargets for &Vec<Entity> {
|
||||||
|
fn components(&self) -> &[ComponentId] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entities(&self) -> &[Entity] {
|
||||||
|
self.as_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue