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  ## 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.
This commit is contained in:
parent
f17644879d
commit
8e84b461a0
@ -277,7 +277,7 @@ trace_tracy_memory = [
|
||||
]
|
||||
|
||||
# Tracing support
|
||||
trace = ["bevy_internal/trace"]
|
||||
trace = ["bevy_internal/trace", "dep:tracing"]
|
||||
|
||||
# Basis Universal compressed texture support
|
||||
basis-universal = ["bevy_internal/basis-universal"]
|
||||
@ -484,6 +484,7 @@ ghost_nodes = ["bevy_internal/ghost_nodes"]
|
||||
|
||||
[dependencies]
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, optional = true }
|
||||
|
||||
# Wasm does not support dynamic linking.
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
|
@ -23,17 +23,21 @@ use bevy::{
|
||||
},
|
||||
log::LogPlugin,
|
||||
prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update},
|
||||
ptr::OwningPtr,
|
||||
ptr::{OwningPtr, PtrMut},
|
||||
MinimalPlugins,
|
||||
};
|
||||
|
||||
use rand::prelude::{Rng, SeedableRng, SliceRandom};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use std::{alloc::Layout, num::Wrapping};
|
||||
use std::{alloc::Layout, mem::ManuallyDrop, num::Wrapping};
|
||||
|
||||
#[expect(unsafe_code, reason = "Reading dynamic components requires unsafe")]
|
||||
// A simple system that matches against several components and does some menial calculation to create
|
||||
// some non-trivial load.
|
||||
fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<FilteredEntityMut>) {
|
||||
#[cfg(feature = "trace")]
|
||||
let _span = tracing::info_span!("base_system", components = ?access_components.0, count = query.iter().len()).entered();
|
||||
|
||||
for mut filtered_entity in &mut query {
|
||||
// We calculate Faulhaber's formula mod 256 with n = value and p = exponent.
|
||||
// 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
|
||||
// find the value of the component
|
||||
let ptr = filtered_entity.get_by_id(*component_id).unwrap();
|
||||
|
||||
#[expect(
|
||||
unsafe_code,
|
||||
reason = "Used to calculate Faulhaber's formula, per the comment above"
|
||||
)]
|
||||
// SAFETY: All components have a u8 layout
|
||||
let value: u8 = unsafe { *ptr.deref::<u8>() };
|
||||
|
||||
@ -65,10 +65,6 @@ fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<Filtere
|
||||
// we assign this value to all the components we can write to
|
||||
for component_id in &access_components.0 {
|
||||
if let Some(ptr) = filtered_entity.get_mut_by_id(*component_id) {
|
||||
#[expect(
|
||||
unsafe_code,
|
||||
reason = "Used to write a value to every component that we can write to."
|
||||
)]
|
||||
// SAFETY: All components have a u8 layout
|
||||
unsafe {
|
||||
let mut value = ptr.with_type::<u8>();
|
||||
@ -79,6 +75,7 @@ fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<Filtere
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(unsafe_code, reason = "Using dynamic components requires unsafe")]
|
||||
fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(42);
|
||||
let mut app = App::default();
|
||||
@ -88,10 +85,9 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
|
||||
let component_ids: Vec<ComponentId> = (1..=num_components)
|
||||
.map(|i| {
|
||||
world.register_component_with_descriptor(
|
||||
#[expect(unsafe_code, reason = "Used to register a bunch of fake components")]
|
||||
// SAFETY:
|
||||
// we don't implement a drop function
|
||||
// u8 is Sync and Send
|
||||
// * We don't implement a drop function
|
||||
// * u8 is Sync and Send
|
||||
unsafe {
|
||||
ComponentDescriptor::new_with_layout(
|
||||
format!("Component{}", i).to_string(),
|
||||
@ -131,26 +127,41 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
|
||||
// spawn a bunch of entities
|
||||
for _ in 1..=num_entities {
|
||||
let num_components = rng.gen_range(1..10);
|
||||
let components = component_ids.choose_multiple(&mut rng, num_components);
|
||||
let components: Vec<ComponentId> = component_ids
|
||||
.choose_multiple(&mut rng, num_components)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
for &component_id in components {
|
||||
let value: u8 = rng.gen_range(0..255);
|
||||
OwningPtr::make(value, |ptr| {
|
||||
#[expect(
|
||||
unsafe_code,
|
||||
reason = "Used to write to a fake component that we previously set up"
|
||||
)]
|
||||
// We use `ManuallyDrop` here as we need to avoid dropping the u8's when `values` is dropped
|
||||
// since ownership of the values is passed to the world in `insert_by_ids`.
|
||||
// But we do want to deallocate the memory when values is dropped.
|
||||
let mut values: Vec<ManuallyDrop<u8>> = components
|
||||
.iter()
|
||||
.map(|_id| ManuallyDrop::new(rng.gen_range(0..255)))
|
||||
.collect();
|
||||
let ptrs: Vec<OwningPtr> = values
|
||||
.iter_mut()
|
||||
.map(|value| {
|
||||
// SAFETY:
|
||||
// component_id is from the same world
|
||||
// value is u8, so ptr is a valid reference for component_id
|
||||
unsafe {
|
||||
entity.insert_by_id(component_id, ptr);
|
||||
}
|
||||
});
|
||||
// * We don't read/write `values` binding after this and values are `ManuallyDrop`,
|
||||
// so we have the right to drop/move the values
|
||||
unsafe { PtrMut::from(value).promote() }
|
||||
})
|
||||
.collect();
|
||||
// SAFETY:
|
||||
// * component_id's are from the same world
|
||||
// * `values` was initialized above, so references are valid
|
||||
unsafe {
|
||||
entity.insert_by_ids(&components, ptrs.into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
"Number of Archetype-Components: {}",
|
||||
world.archetypes().archetype_components_len()
|
||||
);
|
||||
|
||||
// overwrite Update schedule in the app
|
||||
app.add_schedule(schedule);
|
||||
app.add_plugins(MinimalPlugins)
|
||||
|
Loading…
Reference in New Issue
Block a user