Files
bevy/benches/benches/bevy_ecs/main.rs
T
Trashtalk217 a50b65dfa5 Store resources on sparse sets (#24077)
# Objective

Part of the #23988 and #24058 saga. We attempt to speed up resource
access.

## Solution

When messing around with #23988 I noticed that changing the storage type
mattered a lot for the benchmarks.

## Testing

Added benchmarks from #24058 and got the following micro benchmarks
compared to main:

```
ecs::resources::get     time:   [6.3584 ns 6.3840 ns 6.4097 ns]
                        change: [−10.625% −10.075% −9.6652%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  1 (1.00%) low mild
  8 (8.00%) high mild

ecs::resources::get_mut time:   [7.4181 ns 7.4343 ns 7.4515 ns]
                        change: [−39.895% −39.304% −38.809%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  5 (5.00%) high mild
  2 (2.00%) high severe

ecs::resources::insert_remove
                        time:   [89.515 ns 89.654 ns 89.815 ns]
                        change: [−20.163% −16.527% −11.930%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 10 outliers among 100 measurements (10.00%)
  2 (2.00%) low severe
  1 (1.00%) low mild
  3 (3.00%) high mild
  4 (4.00%) high severe
```

If someone wants to double-check these numbers, I encourage you to do
so.
2026-05-04 16:15:20 -07:00

112 lines
3.8 KiB
Rust

#![expect(
dead_code,
reason = "Many fields are unused/unread as they are just for benchmarking purposes."
)]
use criterion::criterion_main;
mod bundles;
mod change_detection;
mod components;
mod empty_archetypes;
mod entity_cloning;
mod events;
mod fragmentation;
mod iteration;
mod observers;
mod param;
mod resources;
mod scheduling;
mod world;
criterion_main!(
bundles::benches,
change_detection::benches,
components::benches,
empty_archetypes::benches,
entity_cloning::benches,
events::benches,
iteration::benches,
fragmentation::benches,
observers::benches,
resources::benches,
scheduling::benches,
world::benches,
param::benches,
);
mod world_builder {
use bevy_ecs::world::World;
use rand::{rngs::SmallRng, seq::SliceRandom, SeedableRng};
/// This builder generates a "hot"/realistic [`World`].
///
/// Using [`World::new`] creates a "cold" world.
/// That is, the world has a fresh entity allocator, no registered components, and generally no accumulated entropy.
/// When a cold world is used in a benchmark, much of what is benched is registration and caching costs,
/// and what is not benched is the cost of the accumulated entropy in world storage, entity allocators, etc.
///
/// Use this in benches that are meant to reflect realistic, common, non-startup scenarios (Ex: spawn scenes, query entities, etc).
/// Prefer [`World::new`] when creating benches for start-up costs (Ex: component registration, table creation time, etc).
///
/// Note that this does have a performance cost over [`World::new`], so this should not be used in a benchmark's routine, only in its setup.
///
/// Which parts of the world are sped up is highly configurable in the interest of doing the minimal work to warm up a world for a particular benchmark.
/// (For example, despawn benches wouldn't benefit from warming up world storage.)
pub struct WorldBuilder {
world: World,
rng: SmallRng,
max_expected_entities: u32,
}
impl WorldBuilder {
/// Starts the builder.
pub fn new() -> Self {
Self {
world: World::new(),
rng: SmallRng::seed_from_u64(2039482342342),
max_expected_entities: 10_000,
}
}
/// Sets the maximum expected entities that will interact with the world.
/// By default this is `10_000`.
pub fn with_max_expected_entities(mut self, max_expected_entities: u32) -> Self {
self.max_expected_entities = max_expected_entities;
self
}
/// Warms up the entity allocator to give out arbitrary entity ids instead of sequential ones.
/// This also pre-allocates room in `Entities`.
pub fn warm_up_entity_allocator(mut self) -> Self {
// allocate
let mut entities = Vec::new();
entities.reserve_exact(self.max_expected_entities as usize);
entities.extend(
self.world
.entity_allocator()
.alloc_many(self.max_expected_entities),
);
// Spawn the high index to warm up `Entities`.
let Some(high_index) = entities.last_mut() else {
// There were no expected entities.
return self;
};
self.world.spawn_empty_at(*high_index).unwrap();
*high_index = self.world.try_despawn_no_free(*high_index).unwrap();
// free
entities.shuffle(&mut self.rng);
self.world.entity_allocator_mut().free_many(&entities);
self
}
/// Finishes the builder to get the warmed up world.
pub fn build(self) -> World {
self.world
}
}
}