diff --git a/benches/Cargo.toml b/benches/Cargo.toml index db2a5b133e1eb..becb6cff4e68e 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -60,11 +60,6 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } unsafe_op_in_unsafe_fn = "warn" unused_qualifications = "warn" -[[bench]] -name = "entity_cloning" -path = "benches/bevy_ecs/entity_cloning.rs" -harness = false - [[bench]] name = "ecs" path = "benches/bevy_ecs/main.rs" diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 218a551584cfb..80577b9a9d0b5 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -1,173 +1,236 @@ use core::hint::black_box; +use benches::bench; use bevy_ecs::bundle::Bundle; +use bevy_ecs::component::ComponentCloneHandler; use bevy_ecs::reflect::AppTypeRegistry; -use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World}; +use bevy_ecs::{component::Component, world::World}; use bevy_hierarchy::{BuildChildren, CloneEntityHierarchyExt}; use bevy_math::Mat4; use bevy_reflect::{GetTypeRegistration, Reflect}; -use criterion::{criterion_group, criterion_main, Bencher, Criterion}; +use criterion::{criterion_group, Bencher, Criterion, Throughput}; -criterion_group!(benches, reflect_benches, clone_benches); -criterion_main!(benches); +criterion_group!( + benches, + single, + hierarchy_tall, + hierarchy_wide, + hierarchy_many, +); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C1(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C2(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C3(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C4(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C5(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C6(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C7(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C8(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C9(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C10(Mat4); type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10); -fn hierarchy( +/// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to +/// use the [`Reflect`] trait instead of [`Clone`]. +fn set_reflect_clone_handler(world: &mut World) { + // Get mutable access to the type registry, creating it if it does not exist yet. + let registry = world.get_resource_or_init::(); + + // Recursively register all components in the bundle to the reflection type registry. + { + let mut r = registry.write(); + r.register::(); + } + + // Recursively register all components in the bundle, then save the component IDs to a list. + // This uses `contributed_components()`, meaning both explicit and required component IDs in + // this bundle are saved. + let component_ids: Vec<_> = world.register_bundle::().contributed_components().into(); + + let clone_handlers = world.get_component_clone_handlers_mut(); + + // Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`. + for component in component_ids { + clone_handlers.set_component_handler(component, ComponentCloneHandler::reflect_handler()); + } +} + +/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a +/// bundle `B`. +/// +/// The bundle must implement [`Default`], which is used to create the first entity that gets cloned +/// in the benchmark. +/// +/// If `clone_via_reflect` is false, this will use the default [`ComponentCloneHandler`] for all +/// components (which is usually [`ComponentCloneHandler::clone_handler()`]). If `clone_via_reflect` +/// is true, it will overwrite the handler for all components in the bundle to be +/// [`ComponentCloneHandler::reflect_handler()`]. +fn bench_clone( b: &mut Bencher, - width: usize, - height: usize, clone_via_reflect: bool, ) { let mut world = World::default(); - let registry = AppTypeRegistry::default(); - { - let mut r = registry.write(); - r.register::(); + + if clone_via_reflect { + set_reflect_clone_handler::(&mut world); } - world.insert_resource(registry); - world.register_bundle::(); + + // Spawn the first entity, which will be cloned in the benchmark routine. + let id = world.spawn(B::default()).id(); + + b.iter(|| { + // Queue the command to clone the entity. + world.commands().entity(black_box(id)).clone_and_spawn(); + + // Run the command. + world.flush(); + }); +} + +/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a +/// bundle `B`. +/// +/// As compared to [`bench_clone()`], this benchmarks recursively cloning an entity with several +/// children. It does so by setting up an entity tree with a given `height` where each entity has a +/// specified number of `children`. +/// +/// For example, setting `height` to 5 and `children` to 1 creates a single chain of entities with +/// no siblings. Alternatively, setting `height` to 1 and `children` to 5 will spawn 5 direct +/// children of the root entity. +fn bench_clone_hierarchy( + b: &mut Bencher, + height: usize, + children: usize, + clone_via_reflect: bool, +) { + let mut world = World::default(); + if clone_via_reflect { - let mut components = Vec::new(); - C::get_component_ids(world.components(), &mut |id| components.push(id.unwrap())); - for component in components { - world - .get_component_clone_handlers_mut() - .set_component_handler( - component, - bevy_ecs::component::ComponentCloneHandler::reflect_handler(), - ); - } + set_reflect_clone_handler::(&mut world); } - let id = world.spawn(black_box(C::default())).id(); + // Spawn the first entity, which will be cloned in the benchmark routine. + let id = world.spawn(B::default()).id(); let mut hierarchy_level = vec![id]; + // Set up the hierarchy tree by spawning all children. for _ in 0..height { let current_hierarchy_level = hierarchy_level.clone(); + hierarchy_level.clear(); + for parent_id in current_hierarchy_level { - for _ in 0..width { - let child_id = world - .spawn(black_box(C::default())) - .set_parent(parent_id) - .id(); + for _ in 0..children { + let child_id = world.spawn(B::default()).set_parent(parent_id).id(); + hierarchy_level.push(child_id); } } } + + // Flush all `set_parent()` commands. world.flush(); - b.iter(move || { - world.commands().entity(id).clone_and_spawn_with(|builder| { - builder.recursive(true); - }); + b.iter(|| { + world + .commands() + .entity(black_box(id)) + .clone_and_spawn_with(|builder| { + // Make the clone command recursive, so children are cloned as well. + builder.recursive(true); + }); + world.flush(); }); } -fn simple(b: &mut Bencher, clone_via_reflect: bool) { - let mut world = World::default(); - let registry = AppTypeRegistry::default(); - { - let mut r = registry.write(); - r.register::(); +// Each benchmark runs twice: using either the `Clone` or `Reflect` traits to clone entities. This +// constant represents this as an easy array that can be used in a `for` loop. +const SCENARIOS: [(&str, bool); 2] = [("clone", false), ("reflect", true)]; + +/// Benchmarks cloning a single entity with 10 components and no children. +fn single(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("single")); + + // We're cloning 1 entity. + group.throughput(Throughput::Elements(1)); + + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone::(b, clone_via_reflect); + }); } - world.insert_resource(registry); - world.register_bundle::(); - if clone_via_reflect { - let mut components = Vec::new(); - C::get_component_ids(world.components(), &mut |id| components.push(id.unwrap())); - for component in components { - world - .get_component_clone_handlers_mut() - .set_component_handler( - component, - bevy_ecs::component::ComponentCloneHandler::reflect_handler(), - ); - } + + group.finish(); +} + +/// Benchmarks cloning an an entity and its 50 descendents, each with only 1 component. +fn hierarchy_tall(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("hierarchy_tall")); + + // We're cloning both the root entity and its 50 descendents. + group.throughput(Throughput::Elements(51)); + + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone_hierarchy::(b, 50, 1, clone_via_reflect); + }); } - let id = world.spawn(black_box(C::default())).id(); - b.iter(move || { - world.commands().entity(id).clone_and_spawn(); - world.flush(); - }); + group.finish(); } -fn reflect_benches(c: &mut Criterion) { - c.bench_function("many components reflect", |b| { - simple::(b, true); - }); +/// Benchmarks cloning an an entity and its 50 direct children, each with only 1 component. +fn hierarchy_wide(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("hierarchy_wide")); - c.bench_function("hierarchy wide reflect", |b| { - hierarchy::(b, 10, 4, true); - }); + // We're cloning both the root entity and its 50 direct children. + group.throughput(Throughput::Elements(51)); - c.bench_function("hierarchy tall reflect", |b| { - hierarchy::(b, 1, 50, true); - }); + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone_hierarchy::(b, 1, 50, clone_via_reflect); + }); + } - c.bench_function("hierarchy many reflect", |b| { - hierarchy::(b, 5, 5, true); - }); + group.finish(); } -fn clone_benches(c: &mut Criterion) { - c.bench_function("many components clone", |b| { - simple::(b, false); - }); +/// Benchmarks cloning a large hierarchy of entities with several children each. Each entity has 10 +/// components. +fn hierarchy_many(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("hierarchy_many")); - c.bench_function("hierarchy wide clone", |b| { - hierarchy::(b, 10, 4, false); - }); + // We're cloning 364 entities total. This number was calculated by manually counting the number + // of entities spawned in `bench_clone_hierarchy()` with a `println!()` statement. :) + group.throughput(Throughput::Elements(364)); - c.bench_function("hierarchy tall clone", |b| { - hierarchy::(b, 1, 50, false); - }); + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone_hierarchy::(b, 5, 3, clone_via_reflect); + }); + } - c.bench_function("hierarchy many clone", |b| { - hierarchy::(b, 5, 5, false); - }); + group.finish(); } diff --git a/benches/benches/bevy_ecs/main.rs b/benches/benches/bevy_ecs/main.rs index 83f0cde0286d6..9fb9e55729e88 100644 --- a/benches/benches/bevy_ecs/main.rs +++ b/benches/benches/bevy_ecs/main.rs @@ -9,6 +9,7 @@ use criterion::criterion_main; mod change_detection; mod components; mod empty_archetypes; +mod entity_cloning; mod events; mod fragmentation; mod iteration; @@ -21,6 +22,7 @@ criterion_main!( change_detection::benches, components::benches, empty_archetypes::benches, + entity_cloning::benches, events::benches, iteration::benches, fragmentation::benches,