bevy/benches/benches/bevy_ecs/world/commands.rs
BD103 17ad855653
Migrate to core::hint::black_box() (#16980)
# Objective

Many of our benchmarks use
[`criterion::black_box()`](https://docs.rs/criterion/latest/criterion/fn.black_box.html),
which is used to prevent the compiler from optimizing away computation
that we're trying to time. This can be slow, though, because
`criterion::black_box()` forces a point read each time it is called
through
[`ptr::road_volatile()`](https://doc.rust-lang.org/stable/std/ptr/fn.read_volatile.html).

In Rust 1.66, the standard library introduced
[`core::hint::black_box()`](https://doc.rust-lang.org/nightly/std/hint/fn.black_box.html)
(and `std::hint::black_box()`). This is an intended replacement for
`criterion`'s version that uses compiler intrinsics instead of volatile
pointer reads, and thus has no runtime overhead. This increases
benchmark accuracy, which is always nice 👍

Note that benchmarks may _appear_ to improve in performance after this
change, but that's just because we are eliminating the pointer read
overhead.

## Solution

- Deny `criterion::black_box` in `clippy.toml`.
- Fix all imports.

## Testing

- `cargo clippy -p benches --benches`
2024-12-29 19:33:42 +00:00

231 lines
7.1 KiB
Rust

use core::hint::black_box;
use bevy_ecs::{
component::Component,
system::Commands,
world::{Command, CommandQueue, World},
};
use criterion::Criterion;
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
#[derive(Component)]
struct C;
pub fn empty_commands(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("empty_commands");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.bench_function("0_entities", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
bencher.iter(|| {
command_queue.apply(&mut world);
});
});
group.finish();
}
pub fn spawn_commands(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("spawn_commands");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for entity_count in (1..5).map(|i| i * 2 * 1000) {
group.bench_function(format!("{}_entities", entity_count), |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
for i in 0..entity_count {
let mut entity = commands.spawn_empty();
entity
.insert_if(A, || black_box(i % 2 == 0))
.insert_if(B, || black_box(i % 3 == 0))
.insert_if(C, || black_box(i % 4 == 0));
if black_box(i % 5 == 0) {
entity.despawn();
}
}
command_queue.apply(&mut world);
});
});
}
group.finish();
}
#[derive(Default, Component)]
struct Matrix([[f32; 4]; 4]);
#[derive(Default, Component)]
struct Vec3([f32; 3]);
pub fn insert_commands(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("insert_commands");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
let entity_count = 10_000;
group.bench_function("insert", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for _ in 0..entity_count {
entities.push(world.spawn_empty().id());
}
bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
for entity in &entities {
commands
.entity(*entity)
.insert((Matrix::default(), Vec3::default()));
}
command_queue.apply(&mut world);
});
});
group.bench_function("insert_or_spawn_batch", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for _ in 0..entity_count {
entities.push(world.spawn_empty().id());
}
bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
let mut values = Vec::with_capacity(entity_count);
for entity in &entities {
values.push((*entity, (Matrix::default(), Vec3::default())));
}
commands.insert_or_spawn_batch(values);
command_queue.apply(&mut world);
});
});
group.bench_function("insert_batch", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for _ in 0..entity_count {
entities.push(world.spawn_empty().id());
}
bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
let mut values = Vec::with_capacity(entity_count);
for entity in &entities {
values.push((*entity, (Matrix::default(), Vec3::default())));
}
commands.insert_batch(values);
command_queue.apply(&mut world);
});
});
group.finish();
}
struct FakeCommandA;
struct FakeCommandB(u64);
impl Command for FakeCommandA {
fn apply(self, world: &mut World) {
black_box(self);
black_box(world);
}
}
impl Command for FakeCommandB {
fn apply(self, world: &mut World) {
black_box(self);
black_box(world);
}
}
pub fn fake_commands(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("fake_commands");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for command_count in (1..5).map(|i| i * 2 * 1000) {
group.bench_function(format!("{}_commands", command_count), |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
for i in 0..command_count {
if black_box(i % 2 == 0) {
commands.queue(FakeCommandA);
} else {
commands.queue(FakeCommandB(0));
}
}
command_queue.apply(&mut world);
});
});
}
group.finish();
}
#[derive(Default)]
struct SizedCommand<T: Default + Send + Sync + 'static>(T);
impl<T: Default + Send + Sync + 'static> Command for SizedCommand<T> {
fn apply(self, world: &mut World) {
black_box(self);
black_box(world);
}
}
struct LargeStruct([u64; 64]);
impl Default for LargeStruct {
fn default() -> Self {
Self([0; 64])
}
}
pub fn sized_commands_impl<T: Default + Command>(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group(format!("sized_commands_{}_bytes", size_of::<T>()));
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for command_count in (1..5).map(|i| i * 2 * 1000) {
group.bench_function(format!("{}_commands", command_count), |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
for _ in 0..command_count {
commands.queue(T::default());
}
command_queue.apply(&mut world);
});
});
}
group.finish();
}
pub fn zero_sized_commands(criterion: &mut Criterion) {
sized_commands_impl::<SizedCommand<()>>(criterion);
}
pub fn medium_sized_commands(criterion: &mut Criterion) {
sized_commands_impl::<SizedCommand<(u32, u32, u32)>>(criterion);
}
pub fn large_sized_commands(criterion: &mut Criterion) {
sized_commands_impl::<SizedCommand<LargeStruct>>(criterion);
}