Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions benches/benches/bevy_ecs/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,80 @@ criterion_main!(
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);
entities
.drain(..)
.for_each(|e| self.world.entity_allocator_mut().free(e));

self
}

/// Finishes the builder to get the warmed up world.
pub fn build(self) -> World {
self.world
}
}
}
24 changes: 19 additions & 5 deletions benches/benches/bevy_ecs/world/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use bevy_ecs::{
};
use criterion::Criterion;

use crate::world_builder::WorldBuilder;

#[derive(Component)]
struct A;
#[derive(Component)]
Expand Down Expand Up @@ -38,7 +40,10 @@ pub fn spawn_commands(criterion: &mut Criterion) {

for entity_count in [100, 1_000, 10_000] {
group.bench_function(format!("{entity_count}_entities"), |bencher| {
let mut world = World::default();
let mut world = WorldBuilder::new()
.with_max_expected_entities(entity_count)
.warm_up_entity_allocator()
.build();
let mut command_queue = CommandQueue::default();

bencher.iter(|| {
Expand Down Expand Up @@ -69,7 +74,10 @@ pub fn nonempty_spawn_commands(criterion: &mut Criterion) {

for entity_count in [100, 1_000, 10_000] {
group.bench_function(format!("{entity_count}_entities"), |bencher| {
let mut world = World::default();
let mut world = WorldBuilder::new()
.with_max_expected_entities(entity_count)
.warm_up_entity_allocator()
.build();
let mut command_queue = CommandQueue::default();

bencher.iter(|| {
Expand Down Expand Up @@ -100,7 +108,10 @@ pub fn insert_commands(criterion: &mut Criterion) {

let entity_count = 10_000;
group.bench_function("insert", |bencher| {
let mut world = World::default();
let mut world = WorldBuilder::new()
.with_max_expected_entities(entity_count)
.warm_up_entity_allocator()
.build();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for _ in 0..entity_count {
Expand All @@ -118,7 +129,10 @@ pub fn insert_commands(criterion: &mut Criterion) {
});
});
group.bench_function("insert_batch", |bencher| {
let mut world = World::default();
let mut world = WorldBuilder::new()
.with_max_expected_entities(entity_count)
.warm_up_entity_allocator()
.build();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for _ in 0..entity_count {
Expand All @@ -127,7 +141,7 @@ pub fn insert_commands(criterion: &mut Criterion) {

bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
let mut values = Vec::with_capacity(entity_count);
let mut values = Vec::with_capacity(entity_count as usize);
for entity in &entities {
values.push((*entity, (Matrix::default(), Vec3::default())));
}
Expand Down
7 changes: 6 additions & 1 deletion benches/benches/bevy_ecs/world/despawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use bevy_ecs::prelude::*;
use criterion::{BatchSize, Criterion};
use glam::*;

use crate::world_builder::WorldBuilder;

#[derive(Component)]
struct A(Mat4);
#[derive(Component)]
Expand All @@ -16,7 +18,10 @@ pub fn world_despawn(criterion: &mut Criterion) {
group.bench_function(format!("{entity_count}_entities"), |bencher| {
bencher.iter_batched_ref(
|| {
let mut world = World::default();
let mut world = WorldBuilder::new()
.with_max_expected_entities(entity_count)
.warm_up_entity_allocator()
.build();
let entities: Vec<Entity> = world
.spawn_batch(
(0..entity_count).map(|_| (A(Mat4::default()), B(Vec4::default()))),
Expand Down
7 changes: 6 additions & 1 deletion benches/benches/bevy_ecs/world/despawn_recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use bevy_ecs::prelude::*;
use criterion::{BatchSize, Criterion};
use glam::*;

use crate::world_builder::WorldBuilder;

#[derive(Component)]
struct A(Mat4);
#[derive(Component)]
Expand All @@ -16,7 +18,10 @@ pub fn world_despawn_recursive(criterion: &mut Criterion) {
group.bench_function(format!("{entity_count}_entities"), |bencher| {
bencher.iter_batched_ref(
|| {
let mut world = World::default();
let mut world = WorldBuilder::new()
.with_max_expected_entities(entity_count)
.warm_up_entity_allocator()
.build();
let parent_ents = (0..entity_count)
.map(|_| {
world
Expand Down
14 changes: 11 additions & 3 deletions benches/benches/bevy_ecs/world/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use bevy_ecs::prelude::*;
use criterion::Criterion;
use glam::*;

use crate::world_builder::WorldBuilder;

#[derive(Component, Clone)]
struct A(Mat4);
#[derive(Component, Clone)]
Expand All @@ -14,7 +16,10 @@ pub fn world_spawn(criterion: &mut Criterion) {

for entity_count in [1, 100, 10_000] {
group.bench_function(format!("{entity_count}_entities"), |bencher| {
let mut world = World::default();
let mut world = WorldBuilder::new()
.with_max_expected_entities(entity_count)
.warm_up_entity_allocator()
.build();
bencher.iter(|| {
for _ in 0..entity_count {
world.spawn((A(Mat4::default()), B(Vec4::default())));
Expand All @@ -33,12 +38,15 @@ pub fn world_spawn_batch(criterion: &mut Criterion) {

for batch_count in [1, 100, 1000, 10_000] {
group.bench_function(format!("{batch_count}_entities"), |bencher| {
let mut world = World::default();
let mut world = WorldBuilder::new()
.with_max_expected_entities(batch_count)
.warm_up_entity_allocator()
.build();
bencher.iter(|| {
for _ in 0..(10_000 / batch_count) {
world.spawn_batch(std::iter::repeat_n(
(A(Mat4::default()), B(Vec4::default())),
batch_count,
batch_count as usize,
));
}
});
Expand Down