From d6376ad10d236e377d2f1c07d2c61dc085eb5e13 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:05:56 -0500 Subject: [PATCH 1/9] fix: merge entity cloning benchmarks into main ecs binary --- benches/Cargo.toml | 5 ----- benches/benches/bevy_ecs/entity_cloning.rs | 3 +-- benches/benches/bevy_ecs/main.rs | 2 ++ 3 files changed, 3 insertions(+), 7 deletions(-) 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..074e658ffce67 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -6,10 +6,9 @@ use bevy_ecs::{component::Component, reflect::ReflectComponent, 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}; criterion_group!(benches, reflect_benches, clone_benches); -criterion_main!(benches); #[derive(Component, Reflect, Default, Clone)] #[reflect(Component)] 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, From 80be73815fd52e75e559b340e9b82c086e11d394 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 30 Dec 2024 18:04:53 -0500 Subject: [PATCH 2/9] feat: apply `bench!` macro In doing so, I've also renamed all the benchmarks and moved them under benchmark groups. --- benches/benches/bevy_ecs/entity_cloning.rs | 31 ++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 074e658ffce67..ea3ff6e82825e 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -1,5 +1,6 @@ use core::hint::black_box; +use benches::bench; use bevy_ecs::bundle::Bundle; use bevy_ecs::reflect::AppTypeRegistry; use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World}; @@ -8,7 +9,7 @@ use bevy_math::Mat4; use bevy_reflect::{GetTypeRegistration, Reflect}; use criterion::{criterion_group, Bencher, Criterion}; -criterion_group!(benches, reflect_benches, clone_benches); +criterion_group!(benches, with_reflect, with_clone); #[derive(Component, Reflect, Default, Clone)] #[reflect(Component)] @@ -135,38 +136,46 @@ fn simple(b: &mut Bencher, clone_via_ }); } -fn reflect_benches(c: &mut Criterion) { - c.bench_function("many components reflect", |b| { +fn with_reflect(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("with_reflect")); + + group.bench_function("simple", |b| { simple::(b, true); }); - c.bench_function("hierarchy wide reflect", |b| { + group.bench_function("hierarchy_wide", |b| { hierarchy::(b, 10, 4, true); }); - c.bench_function("hierarchy tall reflect", |b| { + group.bench_function("hierarchy_tall", |b| { hierarchy::(b, 1, 50, true); }); - c.bench_function("hierarchy many reflect", |b| { + group.bench_function("hierarchy_many", |b| { hierarchy::(b, 5, 5, true); }); + + group.finish(); } -fn clone_benches(c: &mut Criterion) { - c.bench_function("many components clone", |b| { +fn with_clone(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("with_clone")); + + group.bench_function("simple", |b| { simple::(b, false); }); - c.bench_function("hierarchy wide clone", |b| { + group.bench_function("hierarchy_wide", |b| { hierarchy::(b, 10, 4, false); }); - c.bench_function("hierarchy tall clone", |b| { + group.bench_function("hierarchy_tall", |b| { hierarchy::(b, 1, 50, false); }); - c.bench_function("hierarchy many clone", |b| { + group.bench_function("hierarchy_many", |b| { hierarchy::(b, 5, 5, false); }); + + group.finish(); } From bf5333565ac7dc0c71dc4ca8b4db283bcdfcd0c6 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:15:18 -0500 Subject: [PATCH 3/9] refactor: simple benchmark utility - Add lots of comments - Move functions specific to reflection coding behind the `if` gate - Fix `black_box()` being used incorrectly --- benches/benches/bevy_ecs/entity_cloning.rs | 62 +++++++++++++++------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index ea3ff6e82825e..723c9f2984dd8 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -2,7 +2,9 @@ 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::system::EntityCommands; use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World}; use bevy_hierarchy::{BuildChildren, CloneEntityHierarchyExt}; use bevy_math::Mat4; @@ -107,31 +109,51 @@ fn hierarchy( }); } -fn simple(b: &mut Bencher, clone_via_reflect: bool) { +/// 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, clone_via_reflect: bool) { let mut world = World::default(); - let registry = AppTypeRegistry::default(); - { - let mut r = registry.write(); - r.register::(); - } - 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 { + let registry = AppTypeRegistry::default(); + + { + let mut r = registry.write(); + + // Recursively register all components in the bundle to the reflection type registry. + r.register::(); + } + + world.insert_resource(registry); + + // Recursively register all components in the bundle, then save the component IDs to a list. + let component_ids: Vec<_> = world.register_bundle::().contributed_components().into(); + + // Overwrite the clone handler for all components in the bundle to use `Reflect`, not + // `Clone`. + for component in component_ids { world .get_component_clone_handlers_mut() - .set_component_handler( - component, - bevy_ecs::component::ComponentCloneHandler::reflect_handler(), - ); + .set_component_handler(component, ComponentCloneHandler::reflect_handler()); } } - let id = world.spawn(black_box(C::default())).id(); - b.iter(move || { - world.commands().entity(id).clone_and_spawn(); + // 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(); }); } @@ -140,7 +162,7 @@ fn with_reflect(c: &mut Criterion) { let mut group = c.benchmark_group(bench!("with_reflect")); group.bench_function("simple", |b| { - simple::(b, true); + bench_clone::(b, true); }); group.bench_function("hierarchy_wide", |b| { @@ -162,7 +184,7 @@ fn with_clone(c: &mut Criterion) { let mut group = c.benchmark_group(bench!("with_clone")); group.bench_function("simple", |b| { - simple::(b, false); + bench_clone::(b, false); }); group.bench_function("hierarchy_wide", |b| { From 5a45f28b007b8eb52dd31ec26a7f550d41ad0ded Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 1 Jan 2025 15:44:29 -0500 Subject: [PATCH 4/9] refactor: extract reflection clone handler into a separate function --- benches/benches/bevy_ecs/entity_cloning.rs | 77 ++++++++++------------ 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 723c9f2984dd8..67266f5c80934 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -55,6 +55,31 @@ struct C10(Mat4); type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10); +/// 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()); + } +} + fn hierarchy( b: &mut Bencher, width: usize, @@ -62,24 +87,9 @@ fn hierarchy( clone_via_reflect: bool, ) { let mut world = World::default(); - let registry = AppTypeRegistry::default(); - { - let mut r = registry.write(); - r.register::(); - } - 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(), - ); - } + set_reflect_clone_handler::(&mut world); } let id = world.spawn(black_box(C::default())).id(); @@ -99,6 +109,8 @@ fn hierarchy( } } } + + // Flush all `set_parent()` commands. world.flush(); b.iter(move || { @@ -111,39 +123,22 @@ fn hierarchy( /// 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, clone_via_reflect: bool) { +fn bench_clone( + b: &mut Bencher, + clone_via_reflect: bool, +) { let mut world = World::default(); if clone_via_reflect { - let registry = AppTypeRegistry::default(); - - { - let mut r = registry.write(); - - // Recursively register all components in the bundle to the reflection type registry. - r.register::(); - } - - world.insert_resource(registry); - - // Recursively register all components in the bundle, then save the component IDs to a list. - let component_ids: Vec<_> = world.register_bundle::().contributed_components().into(); - - // Overwrite the clone handler for all components in the bundle to use `Reflect`, not - // `Clone`. - for component in component_ids { - world - .get_component_clone_handlers_mut() - .set_component_handler(component, ComponentCloneHandler::reflect_handler()); - } + set_reflect_clone_handler::(&mut world); } // Spawn the first entity, which will be cloned in the benchmark routine. From b2dc33dc9e51a6cdd4213b8363239f300aa4a684 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:48:35 -0500 Subject: [PATCH 5/9] refactor: hierarchy benchmark utility --- benches/benches/bevy_ecs/entity_cloning.rs | 56 ++++++++++++++-------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 67266f5c80934..935f618989c33 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -4,7 +4,6 @@ use benches::bench; use bevy_ecs::bundle::Bundle; use bevy_ecs::component::ComponentCloneHandler; use bevy_ecs::reflect::AppTypeRegistry; -use bevy_ecs::system::EntityCommands; use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World}; use bevy_hierarchy::{BuildChildren, CloneEntityHierarchyExt}; use bevy_math::Mat4; @@ -80,31 +79,43 @@ fn set_reflect_clone_handler(world: &mut World) } } -fn hierarchy( +/// 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, - width: usize, height: usize, + children: usize, clone_via_reflect: bool, ) { let mut world = World::default(); if clone_via_reflect { - set_reflect_clone_handler::(&mut world); + 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); } } @@ -113,10 +124,15 @@ fn hierarchy( // 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(); }); } @@ -161,15 +177,15 @@ fn with_reflect(c: &mut Criterion) { }); group.bench_function("hierarchy_wide", |b| { - hierarchy::(b, 10, 4, true); + bench_clone_hierarchy::(b, 10, 4, true); }); group.bench_function("hierarchy_tall", |b| { - hierarchy::(b, 1, 50, true); + bench_clone_hierarchy::(b, 1, 50, true); }); group.bench_function("hierarchy_many", |b| { - hierarchy::(b, 5, 5, true); + bench_clone_hierarchy::(b, 5, 5, true); }); group.finish(); @@ -183,15 +199,15 @@ fn with_clone(c: &mut Criterion) { }); group.bench_function("hierarchy_wide", |b| { - hierarchy::(b, 10, 4, false); + bench_clone_hierarchy::(b, 10, 4, false); }); group.bench_function("hierarchy_tall", |b| { - hierarchy::(b, 1, 50, false); + bench_clone_hierarchy::(b, 1, 50, false); }); group.bench_function("hierarchy_many", |b| { - hierarchy::(b, 5, 5, false); + bench_clone_hierarchy::(b, 5, 5, false); }); group.finish(); From 7c29f50e56ba2cf95f4fe8d9e9fe2d0fada0e2a7 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:49:37 -0500 Subject: [PATCH 6/9] refactor: reorganize order of code --- benches/benches/bevy_ecs/entity_cloning.rs | 88 +++++++++++----------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 935f618989c33..03184171fb28b 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -79,6 +79,38 @@ fn set_reflect_clone_handler(world: &mut World) } } +/// 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, + clone_via_reflect: bool, +) { + let mut world = World::default(); + + if clone_via_reflect { + set_reflect_clone_handler::(&mut world); + } + + // 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`. /// @@ -137,77 +169,45 @@ fn bench_clone_hierarchy( }); } -/// 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, - clone_via_reflect: bool, -) { - let mut world = World::default(); - - if clone_via_reflect { - set_reflect_clone_handler::(&mut world); - } - - // 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(); - }); -} - -fn with_reflect(c: &mut Criterion) { - let mut group = c.benchmark_group(bench!("with_reflect")); +fn with_clone(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("with_clone")); group.bench_function("simple", |b| { - bench_clone::(b, true); + bench_clone::(b, false); }); group.bench_function("hierarchy_wide", |b| { - bench_clone_hierarchy::(b, 10, 4, true); + bench_clone_hierarchy::(b, 10, 4, false); }); group.bench_function("hierarchy_tall", |b| { - bench_clone_hierarchy::(b, 1, 50, true); + bench_clone_hierarchy::(b, 1, 50, false); }); group.bench_function("hierarchy_many", |b| { - bench_clone_hierarchy::(b, 5, 5, true); + bench_clone_hierarchy::(b, 5, 5, false); }); group.finish(); } -fn with_clone(c: &mut Criterion) { - let mut group = c.benchmark_group(bench!("with_clone")); +fn with_reflect(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("with_reflect")); group.bench_function("simple", |b| { - bench_clone::(b, false); + bench_clone::(b, true); }); group.bench_function("hierarchy_wide", |b| { - bench_clone_hierarchy::(b, 10, 4, false); + bench_clone_hierarchy::(b, 10, 4, true); }); group.bench_function("hierarchy_tall", |b| { - bench_clone_hierarchy::(b, 1, 50, false); + bench_clone_hierarchy::(b, 1, 50, true); }); group.bench_function("hierarchy_many", |b| { - bench_clone_hierarchy::(b, 5, 5, false); + bench_clone_hierarchy::(b, 5, 5, true); }); group.finish(); From 8892266cf5c958cd4bc3c01a8d3509dc62ed9be5 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 1 Jan 2025 20:41:09 -0500 Subject: [PATCH 7/9] feat: rearrange benchmarks into different groups Now they're grouped based on what kind of hierarchy they are, not whether they use `Clone` or `Reflect`. I also deleted all the extra structure definitions, so now each entity has one component of 64 bytes. --- benches/benches/bevy_ecs/entity_cloning.rs | 130 ++++++++++----------- 1 file changed, 61 insertions(+), 69 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 03184171fb28b..80916955112e2 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -8,51 +8,19 @@ use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World}; use bevy_hierarchy::{BuildChildren, CloneEntityHierarchyExt}; use bevy_math::Mat4; use bevy_reflect::{GetTypeRegistration, Reflect}; -use criterion::{criterion_group, Bencher, Criterion}; +use criterion::{criterion_group, Bencher, Criterion, Throughput}; -criterion_group!(benches, with_reflect, with_clone); +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); +struct Foo(Mat4); /// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to /// use the [`Reflect`] trait instead of [`Clone`]. @@ -169,46 +137,70 @@ fn bench_clone_hierarchy( }); } -fn with_clone(c: &mut Criterion) { - let mut group = c.benchmark_group(bench!("with_clone")); +// 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: [(&'static str, bool); 2] = [("clone", false), ("reflect", true)]; - group.bench_function("simple", |b| { - bench_clone::(b, false); - }); +fn single(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("single")); - group.bench_function("hierarchy_wide", |b| { - bench_clone_hierarchy::(b, 10, 4, false); - }); + // We're cloning 1 entity. + group.throughput(Throughput::Elements(1)); - group.bench_function("hierarchy_tall", |b| { - bench_clone_hierarchy::(b, 1, 50, false); - }); + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone::(b, clone_via_reflect); + }); + } - group.bench_function("hierarchy_many", |b| { - bench_clone_hierarchy::(b, 5, 5, false); - }); + group.finish(); +} + +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); + }); + } group.finish(); } -fn with_reflect(c: &mut Criterion) { - let mut group = c.benchmark_group(bench!("with_reflect")); +fn hierarchy_wide(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("hierarchy_wide")); - group.bench_function("simple", |b| { - bench_clone::(b, true); - }); + // We're cloning both the root entity and its 50 direct children. + group.throughput(Throughput::Elements(51)); - group.bench_function("hierarchy_wide", |b| { - bench_clone_hierarchy::(b, 10, 4, true); - }); + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone_hierarchy::(b, 1, 50, clone_via_reflect); + }); + } - group.bench_function("hierarchy_tall", |b| { - bench_clone_hierarchy::(b, 1, 50, true); - }); + group.finish(); +} - group.bench_function("hierarchy_many", |b| { - bench_clone_hierarchy::(b, 5, 5, true); - }); +fn hierarchy_many(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("hierarchy_many")); + + // We're cloning 3,906 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(3906)); + + // The default 5 seconds are not enough here. + group.measurement_time(std::time::Duration::from_secs(8)); + + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone_hierarchy::(b, 5, 5, clone_via_reflect); + }); + } group.finish(); } From e50ab98db0ac22ad3a85d0357d9dbea9a42b3862 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:48:01 -0500 Subject: [PATCH 8/9] fix: benchmark cloning entities with several components - Reintroduce `ComplexBundle`, `C2`, `C3`, `C4`, etc. - Make `single()` and `hierarchy_many()` use `ComplexBundle`. - Add documentation for which benchmarks use 1 or several components. - Decrease the size of the tree in `hierarchy_many()` so it doesn't take 15 seconds. - Remove the `#[reflect(Component)]` attribute from components, since it's not necessary to clone using `Reflect` anymore. --- benches/benches/bevy_ecs/entity_cloning.rs | 56 +++++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 80916955112e2..b14251a01c1c8 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -4,7 +4,7 @@ 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}; @@ -19,8 +19,36 @@ criterion_group!( ); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] -struct Foo(Mat4); +struct C1(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C2(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C3(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C4(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C5(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C6(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C7(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C8(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C9(Mat4); + +#[derive(Component, Reflect, Default, Clone)] +struct C10(Mat4); + +type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10); /// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to /// use the [`Reflect`] trait instead of [`Clone`]. @@ -141,6 +169,7 @@ fn bench_clone_hierarchy( // constant represents this as an easy array that can be used in a `for` loop. const SCENARIOS: [(&'static 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")); @@ -149,13 +178,14 @@ fn single(c: &mut Criterion) { for (id, clone_via_reflect) in SCENARIOS { group.bench_function(id, |b| { - bench_clone::(b, clone_via_reflect); + bench_clone::(b, clone_via_reflect); }); } 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")); @@ -164,13 +194,14 @@ fn hierarchy_tall(c: &mut Criterion) { for (id, clone_via_reflect) in SCENARIOS { group.bench_function(id, |b| { - bench_clone_hierarchy::(b, 50, 1, clone_via_reflect); + bench_clone_hierarchy::(b, 50, 1, clone_via_reflect); }); } group.finish(); } +/// 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")); @@ -179,26 +210,25 @@ fn hierarchy_wide(c: &mut Criterion) { for (id, clone_via_reflect) in SCENARIOS { group.bench_function(id, |b| { - bench_clone_hierarchy::(b, 1, 50, clone_via_reflect); + bench_clone_hierarchy::(b, 1, 50, clone_via_reflect); }); } group.finish(); } +/// 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")); - // We're cloning 3,906 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(3906)); - - // The default 5 seconds are not enough here. - group.measurement_time(std::time::Duration::from_secs(8)); + // 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)); for (id, clone_via_reflect) in SCENARIOS { group.bench_function(id, |b| { - bench_clone_hierarchy::(b, 5, 5, clone_via_reflect); + bench_clone_hierarchy::(b, 5, 3, clone_via_reflect); }); } From 71b77d036a521eedf947a265ec2ed512a9f50641 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:54:46 -0500 Subject: [PATCH 9/9] fix: remove redundant `'static` lifetime --- benches/benches/bevy_ecs/entity_cloning.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index b14251a01c1c8..80577b9a9d0b5 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -167,7 +167,7 @@ fn bench_clone_hierarchy( // 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: [(&'static str, bool); 2] = [("clone", false), ("reflect", true)]; +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) {