Skip to content

Commit 8e84b46

Browse files
authored
many_components stress test improvements (#16913)
# Objective - I was getting familiar with the many_components example to test some recent pr's for executor changes and saw some things to improve. ## Solution - Use `insert_by_ids` instead of `insert_by_id`. This reduces the number of archetype moves and improves startup times substantially. - Add a tracing span to `base_system`. I'm not sure why, but tracing spans weren't showing for this system. I think it's something to do with how pipe system works, but need to investigate more. The approach in this pr is a little better than the default span too, since it allows adding the number of entities queried to the span which is not possible with the default system span. - println the number of archetype component id's that are created. This is useful since part of the purpose of this stress test is to test how well the use of FixedBitSet scales in the executor. ## Testing - Ran the example with `cargo run --example many_components -F trace_tracy 1000000` and connected with tracy - Timed the time it took to spawn 1 million entities on main (240 s) vs this pr (15 s) --- ## Showcase ![image](https://github.com/user-attachments/assets/69da4db0-4ecc-4acb-aebb-2e47d1a35f3b) ## Future Work - Currently systems are created with a random set of components and entities are created with a random set of components without any correlation between the randomness. This means that some systems won't match any entities and some entities could not match any systems. It might be better to spawn the entities from the pool of components that match the queries that the systems are using.
1 parent f176448 commit 8e84b46

File tree

2 files changed

+40
-28
lines changed

2 files changed

+40
-28
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ trace_tracy_memory = [
277277
]
278278

279279
# Tracing support
280-
trace = ["bevy_internal/trace"]
280+
trace = ["bevy_internal/trace", "dep:tracing"]
281281

282282
# Basis Universal compressed texture support
283283
basis-universal = ["bevy_internal/basis-universal"]
@@ -484,6 +484,7 @@ ghost_nodes = ["bevy_internal/ghost_nodes"]
484484

485485
[dependencies]
486486
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
487+
tracing = { version = "0.1", default-features = false, optional = true }
487488

488489
# Wasm does not support dynamic linking.
489490
[target.'cfg(not(target_family = "wasm"))'.dependencies]

examples/stress_tests/many_components.rs

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,21 @@ use bevy::{
2323
},
2424
log::LogPlugin,
2525
prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update},
26-
ptr::OwningPtr,
26+
ptr::{OwningPtr, PtrMut},
2727
MinimalPlugins,
2828
};
2929

3030
use rand::prelude::{Rng, SeedableRng, SliceRandom};
3131
use rand_chacha::ChaCha8Rng;
32-
use std::{alloc::Layout, num::Wrapping};
32+
use std::{alloc::Layout, mem::ManuallyDrop, num::Wrapping};
3333

34+
#[expect(unsafe_code, reason = "Reading dynamic components requires unsafe")]
3435
// A simple system that matches against several components and does some menial calculation to create
3536
// some non-trivial load.
3637
fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<FilteredEntityMut>) {
38+
#[cfg(feature = "trace")]
39+
let _span = tracing::info_span!("base_system", components = ?access_components.0, count = query.iter().len()).entered();
40+
3741
for mut filtered_entity in &mut query {
3842
// We calculate Faulhaber's formula mod 256 with n = value and p = exponent.
3943
// See https://en.wikipedia.org/wiki/Faulhaber%27s_formula
@@ -45,10 +49,6 @@ fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<Filtere
4549
// find the value of the component
4650
let ptr = filtered_entity.get_by_id(*component_id).unwrap();
4751

48-
#[expect(
49-
unsafe_code,
50-
reason = "Used to calculate Faulhaber's formula, per the comment above"
51-
)]
5252
// SAFETY: All components have a u8 layout
5353
let value: u8 = unsafe { *ptr.deref::<u8>() };
5454

@@ -65,10 +65,6 @@ fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<Filtere
6565
// we assign this value to all the components we can write to
6666
for component_id in &access_components.0 {
6767
if let Some(ptr) = filtered_entity.get_mut_by_id(*component_id) {
68-
#[expect(
69-
unsafe_code,
70-
reason = "Used to write a value to every component that we can write to."
71-
)]
7268
// SAFETY: All components have a u8 layout
7369
unsafe {
7470
let mut value = ptr.with_type::<u8>();
@@ -79,6 +75,7 @@ fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<Filtere
7975
}
8076
}
8177

78+
#[expect(unsafe_code, reason = "Using dynamic components requires unsafe")]
8279
fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
8380
let mut rng = ChaCha8Rng::seed_from_u64(42);
8481
let mut app = App::default();
@@ -88,10 +85,9 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
8885
let component_ids: Vec<ComponentId> = (1..=num_components)
8986
.map(|i| {
9087
world.register_component_with_descriptor(
91-
#[expect(unsafe_code, reason = "Used to register a bunch of fake components")]
9288
// SAFETY:
93-
// we don't implement a drop function
94-
// u8 is Sync and Send
89+
// * We don't implement a drop function
90+
// * u8 is Sync and Send
9591
unsafe {
9692
ComponentDescriptor::new_with_layout(
9793
format!("Component{}", i).to_string(),
@@ -131,26 +127,41 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
131127
// spawn a bunch of entities
132128
for _ in 1..=num_entities {
133129
let num_components = rng.gen_range(1..10);
134-
let components = component_ids.choose_multiple(&mut rng, num_components);
130+
let components: Vec<ComponentId> = component_ids
131+
.choose_multiple(&mut rng, num_components)
132+
.copied()
133+
.collect();
135134

136135
let mut entity = world.spawn_empty();
137-
for &component_id in components {
138-
let value: u8 = rng.gen_range(0..255);
139-
OwningPtr::make(value, |ptr| {
140-
#[expect(
141-
unsafe_code,
142-
reason = "Used to write to a fake component that we previously set up"
143-
)]
136+
// We use `ManuallyDrop` here as we need to avoid dropping the u8's when `values` is dropped
137+
// since ownership of the values is passed to the world in `insert_by_ids`.
138+
// But we do want to deallocate the memory when values is dropped.
139+
let mut values: Vec<ManuallyDrop<u8>> = components
140+
.iter()
141+
.map(|_id| ManuallyDrop::new(rng.gen_range(0..255)))
142+
.collect();
143+
let ptrs: Vec<OwningPtr> = values
144+
.iter_mut()
145+
.map(|value| {
144146
// SAFETY:
145-
// component_id is from the same world
146-
// value is u8, so ptr is a valid reference for component_id
147-
unsafe {
148-
entity.insert_by_id(component_id, ptr);
149-
}
150-
});
147+
// * We don't read/write `values` binding after this and values are `ManuallyDrop`,
148+
// so we have the right to drop/move the values
149+
unsafe { PtrMut::from(value).promote() }
150+
})
151+
.collect();
152+
// SAFETY:
153+
// * component_id's are from the same world
154+
// * `values` was initialized above, so references are valid
155+
unsafe {
156+
entity.insert_by_ids(&components, ptrs.into_iter());
151157
}
152158
}
153159

160+
println!(
161+
"Number of Archetype-Components: {}",
162+
world.archetypes().archetype_components_len()
163+
);
164+
154165
// overwrite Update schedule in the app
155166
app.add_schedule(schedule);
156167
app.add_plugins(MinimalPlugins)

0 commit comments

Comments
 (0)