From 46b822e3da25bac8affe17e946e61fc1c07500f1 Mon Sep 17 00:00:00 2001 From: Nathan Ward Date: Mon, 28 Jun 2021 23:26:29 +0000 Subject: [PATCH] `Commands` benchmarking (#2334) # Objective - Currently the `Commands` and `CommandQueue` have no performance testing. - As `Commands` are quite expensive due to the `Box` allocated for each command, there should be perf tests for implementations that attempt to improve the performance. ## Solution - Add some benchmarking for `Commands` and `CommandQueue`. --- benches/Cargo.toml | 5 + benches/benches/bevy_ecs/commands.rs | 177 +++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 benches/benches/bevy_ecs/commands.rs diff --git a/benches/Cargo.toml b/benches/Cargo.toml index ca22ca2f3b835..3d13d202eff82 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -16,6 +16,11 @@ name = "system_stage" path = "benches/bevy_ecs/stages.rs" harness = false +[[bench]] +name = "commands" +path = "benches/bevy_ecs/commands.rs" +harness = false + [[bench]] name = "iter" path = "benches/bevy_tasks/iter.rs" diff --git a/benches/benches/bevy_ecs/commands.rs b/benches/benches/bevy_ecs/commands.rs new file mode 100644 index 0000000000000..57ba249f581fa --- /dev/null +++ b/benches/benches/bevy_ecs/commands.rs @@ -0,0 +1,177 @@ +use bevy::ecs::{ + system::{Command, CommandQueue, Commands}, + world::World, +}; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +criterion_group!( + benches, + empty_commands, + spawn_commands, + fake_commands, + zero_sized_commands, + medium_sized_commands, + large_sized_commands +); +criterion_main!(benches); + +struct A; +struct B; +struct C; + +fn empty_commands(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("empty_commands"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::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(); +} + +fn spawn_commands(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("spawn_commands"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::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(); + + if black_box(i % 2 == 0) { + entity.insert(A); + } + + if black_box(i % 3 == 0) { + entity.insert(B); + } + + if black_box(i % 4 == 0) { + entity.insert(C); + } + + if black_box(i % 5 == 0) { + entity.despawn(); + } + } + drop(commands); + command_queue.apply(&mut world); + }); + }); + } + + group.finish(); +} + +struct FakeCommandA; +struct FakeCommandB(u64); + +impl Command for FakeCommandA { + fn write(self: Box, world: &mut World) { + black_box(self); + black_box(world); + } +} + +impl Command for FakeCommandB { + fn write(self: Box, world: &mut World) { + black_box(self); + black_box(world); + } +} + +fn fake_commands(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("fake_commands"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::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.add(FakeCommandA); + } else { + commands.add(FakeCommandB(0)); + } + } + drop(commands); + command_queue.apply(&mut world); + }); + }); + } + + group.finish(); +} + +#[derive(Default)] +struct SizedCommand(T); + +impl Command for SizedCommand { + fn write(self: Box, world: &mut World) { + black_box(self); + black_box(world); + } +} + +struct LargeStruct([u64; 64]); + +impl Default for LargeStruct { + fn default() -> Self { + Self([0; 64]) + } +} + +fn sized_commands_impl(criterion: &mut Criterion) { + let mut group = + criterion.benchmark_group(format!("sized_commands_{}_bytes", std::mem::size_of::())); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::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.add(T::default()); + } + drop(commands); + command_queue.apply(&mut world); + }); + }); + } + + group.finish(); +} + +fn zero_sized_commands(criterion: &mut Criterion) { + sized_commands_impl::>(criterion); +} + +fn medium_sized_commands(criterion: &mut Criterion) { + sized_commands_impl::>(criterion); +} + +fn large_sized_commands(criterion: &mut Criterion) { + sized_commands_impl::>(criterion); +}