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:
re0312 2024-07-19 02:25:33 +08:00 committed by GitHub
parent dcbd30200e
commit e5bf59d712
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 126 additions and 88 deletions

View file

@ -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);

View file

@ -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>);
}
} }

View 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);
}

View file

@ -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()
}
}