Migrate reflection benchmarks to new naming system (#16986)

# Objective

- Please see #16647 for the full reasoning behind this change.

## Solution

- Create the `bench!` macro, which generates the name of the benchmark
at compile time.

Migrating is a single line change, and it will automatically update if
you move the benchmark to a different module:

  ```diff
  + use benches::bench;

  fn my_benchmark(c: &mut Criterion) {
  -   c.bench_function("my_benchmark", |b| {});
  +   c.bench_function(bench!("my_benchmark"), |b| {});
  }
  ```

- Migrate all reflection benchmarks to use `bench!`.
- Fix a few places where `black_box()` or Criterion is misused.

## Testing

```sh
cd benches

# Will take a long time!
cargo bench --bench reflect

# List out the names of all reflection benchmarks, to ensure I didn't miss anything.
cargo bench --bench reflect -- --list

# Check for linter warnings.
cargo clippy --bench reflect

# Run each benchmark once.
cargo test --bench reflect
```
This commit is contained in:
BD103 2024-12-26 17:28:09 -05:00 committed by GitHub
parent 3eae8590cc
commit c03e494a26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 263 additions and 166 deletions

View File

@ -7,6 +7,11 @@ license = "MIT OR Apache-2.0"
# Do not automatically discover benchmarks, we specify them manually instead. # Do not automatically discover benchmarks, we specify them manually instead.
autobenches = false autobenches = false
[dependencies]
# The primary crate that runs and analyzes our benchmarks. This is a regular dependency because the
# `bench!` macro refers to it in its documentation.
criterion = { version = "0.5.1", features = ["html_reports"] }
[dev-dependencies] [dev-dependencies]
# Bevy crates # Bevy crates
bevy_app = { path = "../crates/bevy_app" } bevy_app = { path = "../crates/bevy_app" }
@ -22,7 +27,6 @@ bevy_tasks = { path = "../crates/bevy_tasks" }
bevy_utils = { path = "../crates/bevy_utils" } bevy_utils = { path = "../crates/bevy_utils" }
# Other crates # Other crates
criterion = { version = "0.5.1", features = ["html_reports"] }
glam = "0.29" glam = "0.29"
rand = "0.8" rand = "0.8"
rand_chacha = "0.3" rand_chacha = "0.3"

View File

@ -1,14 +1,25 @@
use bevy_reflect::func::{ArgList, IntoFunction, IntoFunctionMut, TypedFunction}; use core::hint::black_box;
use criterion::{criterion_group, BatchSize, Criterion};
criterion_group!(benches, typed, into, call, overload, clone); use benches::bench;
use bevy_reflect::func::{ArgList, IntoFunction, IntoFunctionMut, TypedFunction};
use criterion::{criterion_group, BatchSize, BenchmarkId, Criterion};
criterion_group!(
benches,
typed,
into,
call,
clone,
with_overload,
call_overload,
);
fn add(a: i32, b: i32) -> i32 { fn add(a: i32, b: i32) -> i32 {
a + b a + b
} }
fn typed(c: &mut Criterion) { fn typed(c: &mut Criterion) {
c.benchmark_group("typed") c.benchmark_group(bench!("typed"))
.bench_function("function", |b| { .bench_function("function", |b| {
b.iter(|| add.get_function_info()); b.iter(|| add.get_function_info());
}) })
@ -25,7 +36,7 @@ fn typed(c: &mut Criterion) {
} }
fn into(c: &mut Criterion) { fn into(c: &mut Criterion) {
c.benchmark_group("into") c.benchmark_group(bench!("into"))
.bench_function("function", |b| { .bench_function("function", |b| {
b.iter(|| add.into_function()); b.iter(|| add.into_function());
}) })
@ -36,17 +47,18 @@ fn into(c: &mut Criterion) {
}) })
.bench_function("closure_mut", |b| { .bench_function("closure_mut", |b| {
let mut _capture = 25; let mut _capture = 25;
// `move` is required here because `into_function_mut()` takes ownership of `self`.
let closure = move |a: i32| _capture += a; let closure = move |a: i32| _capture += a;
b.iter(|| closure.into_function_mut()); b.iter(|| closure.into_function_mut());
}); });
} }
fn call(c: &mut Criterion) { fn call(c: &mut Criterion) {
c.benchmark_group("call") c.benchmark_group(bench!("call"))
.bench_function("trait_object", |b| { .bench_function("trait_object", |b| {
b.iter_batched( b.iter_batched(
|| Box::new(add) as Box<dyn Fn(i32, i32) -> i32>, || Box::new(add) as Box<dyn Fn(i32, i32) -> i32>,
|func| func(75, 25), |func| func(black_box(75), black_box(25)),
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
@ -78,35 +90,43 @@ fn call(c: &mut Criterion) {
}); });
} }
fn overload(c: &mut Criterion) { fn clone(c: &mut Criterion) {
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T { c.benchmark_group(bench!("clone"))
a + b .bench_function("function", |b| {
} let add = add.into_function();
b.iter(|| add.clone());
});
}
#[expect(clippy::too_many_arguments)] fn simple<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
fn complex<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>( a + b
_: T0, }
_: T1,
_: T2,
_: T3,
_: T4,
_: T5,
_: T6,
_: T7,
_: T8,
_: T9,
) {
}
c.benchmark_group("with_overload") #[expect(clippy::too_many_arguments)]
.bench_function("01_simple_overload", |b| { fn complex<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(
_: T0,
_: T1,
_: T2,
_: T3,
_: T4,
_: T5,
_: T6,
_: T7,
_: T8,
_: T9,
) {
}
fn with_overload(c: &mut Criterion) {
c.benchmark_group(bench!("with_overload"))
.bench_function(BenchmarkId::new("simple_overload", 1), |b| {
b.iter_batched( b.iter_batched(
|| add::<i8>.into_function(), || simple::<i8>.into_function(),
|func| func.with_overload(add::<i16>), |func| func.with_overload(simple::<i16>),
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("01_complex_overload", |b| { .bench_function(BenchmarkId::new("complex_overload", 1), |b| {
b.iter_batched( b.iter_batched(
|| complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(), || complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(),
|func| { |func| {
@ -115,18 +135,18 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("03_simple_overload", |b| { .bench_function(BenchmarkId::new("simple_overload", 3), |b| {
b.iter_batched( b.iter_batched(
|| add::<i8>.into_function(), || simple::<i8>.into_function(),
|func| { |func| {
func.with_overload(add::<i16>) func.with_overload(simple::<i16>)
.with_overload(add::<i32>) .with_overload(simple::<i32>)
.with_overload(add::<i64>) .with_overload(simple::<i64>)
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("03_complex_overload", |b| { .bench_function(BenchmarkId::new("complex_overload", 3), |b| {
b.iter_batched( b.iter_batched(
|| complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(), || complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(),
|func| { |func| {
@ -137,24 +157,24 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("10_simple_overload", |b| { .bench_function(BenchmarkId::new("simple_overload", 10), |b| {
b.iter_batched( b.iter_batched(
|| add::<i8>.into_function(), || simple::<i8>.into_function(),
|func| { |func| {
func.with_overload(add::<i16>) func.with_overload(simple::<i16>)
.with_overload(add::<i32>) .with_overload(simple::<i32>)
.with_overload(add::<i64>) .with_overload(simple::<i64>)
.with_overload(add::<i128>) .with_overload(simple::<i128>)
.with_overload(add::<u8>) .with_overload(simple::<u8>)
.with_overload(add::<u16>) .with_overload(simple::<u16>)
.with_overload(add::<u32>) .with_overload(simple::<u32>)
.with_overload(add::<u64>) .with_overload(simple::<u64>)
.with_overload(add::<u128>) .with_overload(simple::<u128>)
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("10_complex_overload", |b| { .bench_function(BenchmarkId::new("complex_overload", 10), |b| {
b.iter_batched( b.iter_batched(
|| complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(), || complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(),
|func| { |func| {
@ -171,41 +191,41 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("01_nested_simple_overload", |b| { .bench_function(BenchmarkId::new("nested_simple_overload", 1), |b| {
b.iter_batched( b.iter_batched(
|| add::<i8>.into_function(), || simple::<i8>.into_function(),
|func| func.with_overload(add::<i16>), |func| func.with_overload(simple::<i16>),
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("03_nested_simple_overload", |b| { .bench_function(BenchmarkId::new("nested_simple_overload", 3), |b| {
b.iter_batched( b.iter_batched(
|| add::<i8>.into_function(), || simple::<i8>.into_function(),
|func| { |func| {
func.with_overload( func.with_overload(
add::<i16> simple::<i16>.into_function().with_overload(
.into_function() simple::<i32>.into_function().with_overload(simple::<i64>),
.with_overload(add::<i32>.into_function().with_overload(add::<i64>)), ),
) )
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("10_nested_simple_overload", |b| { .bench_function(BenchmarkId::new("nested_simple_overload", 10), |b| {
b.iter_batched( b.iter_batched(
|| add::<i8>.into_function(), || simple::<i8>.into_function(),
|func| { |func| {
func.with_overload( func.with_overload(
add::<i16>.into_function().with_overload( simple::<i16>.into_function().with_overload(
add::<i32>.into_function().with_overload( simple::<i32>.into_function().with_overload(
add::<i64>.into_function().with_overload( simple::<i64>.into_function().with_overload(
add::<i128>.into_function().with_overload( simple::<i128>.into_function().with_overload(
add::<u8>.into_function().with_overload( simple::<u8>.into_function().with_overload(
add::<u16>.into_function().with_overload( simple::<u16>.into_function().with_overload(
add::<u32>.into_function().with_overload( simple::<u32>.into_function().with_overload(
add::<u64> simple::<u64>
.into_function() .into_function()
.with_overload(add::<u128>), .with_overload(simple::<u128>),
), ),
), ),
), ),
@ -218,13 +238,15 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
}
c.benchmark_group("call_overload") fn call_overload(c: &mut Criterion) {
.bench_function("01_simple_overload", |b| { c.benchmark_group(bench!("call_overload"))
.bench_function(BenchmarkId::new("simple_overload", 1), |b| {
b.iter_batched( b.iter_batched(
|| { || {
( (
add::<i8>.into_function().with_overload(add::<i16>), simple::<i8>.into_function().with_overload(simple::<i16>),
ArgList::new().push_owned(75_i8).push_owned(25_i8), ArgList::new().push_owned(75_i8).push_owned(25_i8),
) )
}, },
@ -232,7 +254,7 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("01_complex_overload", |b| { .bench_function(BenchmarkId::new("complex_overload", 1), |b| {
b.iter_batched( b.iter_batched(
|| { || {
( (
@ -258,15 +280,15 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("03_simple_overload", |b| { .bench_function(BenchmarkId::new("simple_overload", 3), |b| {
b.iter_batched( b.iter_batched(
|| { || {
( (
add::<i8> simple::<i8>
.into_function() .into_function()
.with_overload(add::<i16>) .with_overload(simple::<i16>)
.with_overload(add::<i32>) .with_overload(simple::<i32>)
.with_overload(add::<i64>), .with_overload(simple::<i64>),
ArgList::new().push_owned(75_i32).push_owned(25_i32), ArgList::new().push_owned(75_i32).push_owned(25_i32),
) )
}, },
@ -274,7 +296,7 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("03_complex_overload", |b| { .bench_function(BenchmarkId::new("complex_overload", 3), |b| {
b.iter_batched( b.iter_batched(
|| { || {
( (
@ -306,21 +328,21 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("10_simple_overload", |b| { .bench_function(BenchmarkId::new("simple_overload", 10), |b| {
b.iter_batched( b.iter_batched(
|| { || {
( (
add::<i8> simple::<i8>
.into_function() .into_function()
.with_overload(add::<i16>) .with_overload(simple::<i16>)
.with_overload(add::<i32>) .with_overload(simple::<i32>)
.with_overload(add::<i64>) .with_overload(simple::<i64>)
.with_overload(add::<i128>) .with_overload(simple::<i128>)
.with_overload(add::<u8>) .with_overload(simple::<u8>)
.with_overload(add::<u16>) .with_overload(simple::<u16>)
.with_overload(add::<u32>) .with_overload(simple::<u32>)
.with_overload(add::<u64>) .with_overload(simple::<u64>)
.with_overload(add::<u128>), .with_overload(simple::<u128>),
ArgList::new().push_owned(75_u8).push_owned(25_u8), ArgList::new().push_owned(75_u8).push_owned(25_u8),
) )
}, },
@ -328,7 +350,7 @@ fn overload(c: &mut Criterion) {
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}) })
.bench_function("10_complex_overload", |b| { .bench_function(BenchmarkId::new("complex_overload", 10), |b| {
b.iter_batched( b.iter_batched(
|| { || {
( (
@ -379,10 +401,3 @@ fn overload(c: &mut Criterion) {
); );
}); });
} }
fn clone(c: &mut Criterion) {
c.benchmark_group("clone").bench_function("function", |b| {
let add = add.into_function();
b.iter(|| add.clone());
});
}

View File

@ -1,9 +1,10 @@
use core::{iter, time::Duration}; use core::{iter, time::Duration};
use benches::bench;
use bevy_reflect::{DynamicList, List}; use bevy_reflect::{DynamicList, List};
use criterion::{ use criterion::{
black_box, criterion_group, measurement::Measurement, BatchSize, BenchmarkGroup, BenchmarkId, black_box, criterion_group, measurement::Measurement, AxisScale, BatchSize, BenchmarkGroup,
Criterion, Throughput, BenchmarkId, Criterion, PlotConfiguration, Throughput,
}; };
criterion_group!( criterion_group!(
@ -14,11 +15,29 @@ criterion_group!(
dynamic_list_push dynamic_list_push
); );
// Use a shorter warm-up time (from 3 to 0.5 seconds) and measurement time (from 5 to 4) because we
// have so many combinations (>50) to benchmark.
const WARM_UP_TIME: Duration = Duration::from_millis(500); const WARM_UP_TIME: Duration = Duration::from_millis(500);
const MEASUREMENT_TIME: Duration = Duration::from_secs(4); const MEASUREMENT_TIME: Duration = Duration::from_secs(4);
// log10 scaling /// An array of list sizes used in benchmarks.
const SIZES: [usize; 5] = [100_usize, 316, 1000, 3162, 10000]; ///
/// This scales logarithmically.
const SIZES: [usize; 5] = [100, 316, 1000, 3162, 10000];
/// Creates a [`BenchmarkGroup`] with common configuration shared by all benchmarks within this
/// module.
fn create_group<'a, M: Measurement>(c: &'a mut Criterion<M>, name: &str) -> BenchmarkGroup<'a, M> {
let mut group = c.benchmark_group(name);
group
.warm_up_time(WARM_UP_TIME)
.measurement_time(MEASUREMENT_TIME)
// Make the plots logarithmic, matching `SIZES`' scale.
.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));
group
}
fn list_apply<M, LBase, LPatch, F1, F2, F3>( fn list_apply<M, LBase, LPatch, F1, F2, F3>(
group: &mut BenchmarkGroup<M>, group: &mut BenchmarkGroup<M>,
@ -53,9 +72,7 @@ fn list_apply<M, LBase, LPatch, F1, F2, F3>(
} }
fn concrete_list_apply(criterion: &mut Criterion) { fn concrete_list_apply(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("concrete_list_apply"); let mut group = create_group(criterion, bench!("concrete_list_apply"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let empty_base = |_: usize| Vec::<u64>::new; let empty_base = |_: usize| Vec::<u64>::new;
let full_base = |size: usize| move || iter::repeat(0).take(size).collect::<Vec<u64>>(); let full_base = |size: usize| move || iter::repeat(0).take(size).collect::<Vec<u64>>();
@ -77,9 +94,7 @@ fn concrete_list_apply(criterion: &mut Criterion) {
} }
fn concrete_list_clone_dynamic(criterion: &mut Criterion) { fn concrete_list_clone_dynamic(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("concrete_list_clone_dynamic"); let mut group = create_group(criterion, bench!("concrete_list_clone_dynamic"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
for size in SIZES { for size in SIZES {
group.throughput(Throughput::Elements(size as u64)); group.throughput(Throughput::Elements(size as u64));
@ -99,9 +114,7 @@ fn concrete_list_clone_dynamic(criterion: &mut Criterion) {
} }
fn dynamic_list_push(criterion: &mut Criterion) { fn dynamic_list_push(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_list_push"); let mut group = create_group(criterion, bench!("dynamic_list_push"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
for size in SIZES { for size in SIZES {
group.throughput(Throughput::Elements(size as u64)); group.throughput(Throughput::Elements(size as u64));
@ -130,9 +143,7 @@ fn dynamic_list_push(criterion: &mut Criterion) {
} }
fn dynamic_list_apply(criterion: &mut Criterion) { fn dynamic_list_apply(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_list_apply"); let mut group = create_group(criterion, bench!("dynamic_list_apply"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let empty_base = |_: usize| || Vec::<u64>::new().clone_dynamic(); let empty_base = |_: usize| || Vec::<u64>::new().clone_dynamic();
let full_base = |size: usize| move || iter::repeat(0).take(size).collect::<Vec<u64>>(); let full_base = |size: usize| move || iter::repeat(0).take(size).collect::<Vec<u64>>();

View File

@ -1,10 +1,11 @@
use core::{fmt::Write, iter, time::Duration}; use core::{fmt::Write, iter, time::Duration};
use benches::bench;
use bevy_reflect::{DynamicMap, Map}; use bevy_reflect::{DynamicMap, Map};
use bevy_utils::HashMap; use bevy_utils::HashMap;
use criterion::{ use criterion::{
black_box, criterion_group, measurement::Measurement, BatchSize, BenchmarkGroup, BenchmarkId, black_box, criterion_group, measurement::Measurement, AxisScale, BatchSize, BenchmarkGroup,
Criterion, Throughput, BenchmarkId, Criterion, PlotConfiguration, Throughput,
}; };
criterion_group!( criterion_group!(
@ -15,10 +16,30 @@ criterion_group!(
dynamic_map_insert dynamic_map_insert
); );
// Use a shorter warm-up time (from 3 to 0.5 seconds) and measurement time (from 5 to 4) because we
// have so many combinations (>50) to benchmark.
const WARM_UP_TIME: Duration = Duration::from_millis(500); const WARM_UP_TIME: Duration = Duration::from_millis(500);
const MEASUREMENT_TIME: Duration = Duration::from_secs(4); const MEASUREMENT_TIME: Duration = Duration::from_secs(4);
/// An array of list sizes used in benchmarks.
///
/// This scales logarithmically.
const SIZES: [usize; 5] = [100, 316, 1000, 3162, 10000]; const SIZES: [usize; 5] = [100, 316, 1000, 3162, 10000];
/// Creates a [`BenchmarkGroup`] with common configuration shared by all benchmarks within this
/// module.
fn create_group<'a, M: Measurement>(c: &'a mut Criterion<M>, name: &str) -> BenchmarkGroup<'a, M> {
let mut group = c.benchmark_group(name);
group
.warm_up_time(WARM_UP_TIME)
.measurement_time(MEASUREMENT_TIME)
// Make the plots logarithmic, matching `SIZES`' scale.
.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));
group
}
/// Generic benchmark for applying one `Map` to another. /// Generic benchmark for applying one `Map` to another.
/// ///
/// `f_base` is a function which takes an input size and produces a generator /// `f_base` is a function which takes an input size and produces a generator
@ -55,9 +76,7 @@ fn map_apply<M, MBase, MPatch, F1, F2, F3>(
} }
fn concrete_map_apply(criterion: &mut Criterion) { fn concrete_map_apply(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("concrete_map_apply"); let mut group = create_group(criterion, bench!("concrete_map_apply"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let empty_base = |_: usize| HashMap::<u64, u64>::default; let empty_base = |_: usize| HashMap::<u64, u64>::default;
@ -131,9 +150,7 @@ fn u64_to_n_byte_key(k: u64, n: usize) -> String {
} }
fn dynamic_map_apply(criterion: &mut Criterion) { fn dynamic_map_apply(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_map_apply"); let mut group = create_group(criterion, bench!("dynamic_map_apply"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let empty_base = |_: usize| DynamicMap::default; let empty_base = |_: usize| DynamicMap::default;
@ -199,9 +216,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
} }
fn dynamic_map_get(criterion: &mut Criterion) { fn dynamic_map_get(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_map_get"); let mut group = create_group(criterion, bench!("dynamic_map_get"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
for size in SIZES { for size in SIZES {
group.throughput(Throughput::Elements(size as u64)); group.throughput(Throughput::Elements(size as u64));
@ -250,9 +265,7 @@ fn dynamic_map_get(criterion: &mut Criterion) {
} }
fn dynamic_map_insert(criterion: &mut Criterion) { fn dynamic_map_insert(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_map_insert"); let mut group = create_group(criterion, bench!("dynamic_map_insert"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
for size in SIZES { for size in SIZES {
group.throughput(Throughput::Elements(size as u64)); group.throughput(Throughput::Elements(size as u64));

View File

@ -1,5 +1,6 @@
use core::{fmt::Write, str, time::Duration}; use core::{fmt::Write, str, time::Duration};
use benches::bench;
use bevy_reflect::ParsedPath; use bevy_reflect::ParsedPath;
use criterion::{black_box, criterion_group, BatchSize, BenchmarkId, Criterion, Throughput}; use criterion::{black_box, criterion_group, BatchSize, BenchmarkId, Criterion, Throughput};
use rand::{distributions::Uniform, Rng, SeedableRng}; use rand::{distributions::Uniform, Rng, SeedableRng};
@ -11,7 +12,7 @@ const WARM_UP_TIME: Duration = Duration::from_millis(500);
const MEASUREMENT_TIME: Duration = Duration::from_secs(2); const MEASUREMENT_TIME: Duration = Duration::from_secs(2);
const SAMPLE_SIZE: usize = 500; const SAMPLE_SIZE: usize = 500;
const NOISE_THRESHOLD: f64 = 0.03; const NOISE_THRESHOLD: f64 = 0.03;
const SIZES: [usize; 6] = [100, 3160, 1000, 3_162, 10_000, 24_000]; const SIZES: [usize; 6] = [100, 316, 1_000, 3_162, 10_000, 24_000];
fn deterministic_rand() -> ChaCha8Rng { fn deterministic_rand() -> ChaCha8Rng {
ChaCha8Rng::seed_from_u64(42) ChaCha8Rng::seed_from_u64(42)
@ -66,23 +67,32 @@ fn mk_paths(size: usize) -> impl FnMut() -> String {
} }
fn parse_reflect_path(criterion: &mut Criterion) { fn parse_reflect_path(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("parse_reflect_path"); let mut group = criterion.benchmark_group(bench!("parse_reflect_path"));
group.warm_up_time(WARM_UP_TIME); group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME); group.measurement_time(MEASUREMENT_TIME);
group.sample_size(SAMPLE_SIZE); group.sample_size(SAMPLE_SIZE);
group.noise_threshold(NOISE_THRESHOLD); group.noise_threshold(NOISE_THRESHOLD);
let group = &mut group;
for size in SIZES { for size in SIZES {
group.throughput(Throughput::Elements(size as u64)); group.throughput(Throughput::Elements(size as u64));
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("parse_reflect_path", size), BenchmarkId::from_parameter(size),
&size, &size,
|bencher, &size| { |bencher, &size| {
let mk_paths = mk_paths(size); let mk_paths = mk_paths(size);
bencher.iter_batched( bencher.iter_batched(
mk_paths, mk_paths,
|path| assert!(ParsedPath::parse(black_box(&path)).is_ok()), |path| {
let parsed_path = black_box(ParsedPath::parse(black_box(&path)));
// When `cargo test --benches` is run, each benchmark is run once. This
// verifies that we are benchmarking a successful parse without it
// affecting the recorded time.
#[cfg(test)]
assert!(parsed_path.is_ok());
},
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}, },

View File

@ -1,7 +1,11 @@
use core::time::Duration; use core::time::Duration;
use benches::bench;
use bevy_reflect::{DynamicStruct, GetField, PartialReflect, Reflect, Struct}; use bevy_reflect::{DynamicStruct, GetField, PartialReflect, Reflect, Struct};
use criterion::{black_box, criterion_group, BatchSize, BenchmarkId, Criterion, Throughput}; use criterion::{
black_box, criterion_group, measurement::Measurement, AxisScale, BatchSize, BenchmarkGroup,
BenchmarkId, Criterion, PlotConfiguration, Throughput,
};
criterion_group!( criterion_group!(
benches, benches,
@ -19,10 +23,22 @@ const WARM_UP_TIME: Duration = Duration::from_millis(500);
const MEASUREMENT_TIME: Duration = Duration::from_secs(4); const MEASUREMENT_TIME: Duration = Duration::from_secs(4);
const SIZES: [usize; 4] = [16, 32, 64, 128]; const SIZES: [usize; 4] = [16, 32, 64, 128];
/// Creates a [`BenchmarkGroup`] with common configuration shared by all benchmarks within this
/// module.
fn create_group<'a, M: Measurement>(c: &'a mut Criterion<M>, name: &str) -> BenchmarkGroup<'a, M> {
let mut group = c.benchmark_group(name);
group
.warm_up_time(WARM_UP_TIME)
.measurement_time(MEASUREMENT_TIME)
// Make the plots logarithmic, matching `SIZES`' scale.
.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));
group
}
fn concrete_struct_field(criterion: &mut Criterion) { fn concrete_struct_field(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("concrete_struct_field"); let mut group = create_group(criterion, bench!("concrete_struct_field"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let structs: [Box<dyn Struct>; 4] = [ let structs: [Box<dyn Struct>; 4] = [
Box::new(Struct16::default()), Box::new(Struct16::default()),
@ -44,7 +60,7 @@ fn concrete_struct_field(criterion: &mut Criterion) {
bencher.iter(|| { bencher.iter(|| {
for name in &field_names { for name in &field_names {
s.field(black_box(name)); black_box(s.field(black_box(name)));
} }
}); });
}, },
@ -53,9 +69,7 @@ fn concrete_struct_field(criterion: &mut Criterion) {
} }
fn concrete_struct_apply(criterion: &mut Criterion) { fn concrete_struct_apply(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("concrete_struct_apply"); let mut group = create_group(criterion, bench!("concrete_struct_apply"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
// Use functions that produce trait objects of varying concrete types as the // Use functions that produce trait objects of varying concrete types as the
// input to the benchmark. // input to the benchmark.
@ -111,9 +125,7 @@ fn concrete_struct_apply(criterion: &mut Criterion) {
} }
fn concrete_struct_type_info(criterion: &mut Criterion) { fn concrete_struct_type_info(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("concrete_struct_type_info"); let mut group = create_group(criterion, bench!("concrete_struct_type_info"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let structs: [(Box<dyn Struct>, Box<dyn Struct>); 5] = [ let structs: [(Box<dyn Struct>, Box<dyn Struct>); 5] = [
( (
@ -145,23 +157,21 @@ fn concrete_struct_type_info(criterion: &mut Criterion) {
BenchmarkId::new("NonGeneric", field_count), BenchmarkId::new("NonGeneric", field_count),
&standard, &standard,
|bencher, s| { |bencher, s| {
bencher.iter(|| black_box(s.get_represented_type_info())); bencher.iter(|| s.get_represented_type_info());
}, },
); );
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("Generic", field_count), BenchmarkId::new("Generic", field_count),
&generic, &generic,
|bencher, s| { |bencher, s| {
bencher.iter(|| black_box(s.get_represented_type_info())); bencher.iter(|| s.get_represented_type_info());
}, },
); );
} }
} }
fn concrete_struct_clone(criterion: &mut Criterion) { fn concrete_struct_clone(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("concrete_struct_clone"); let mut group = create_group(criterion, bench!("concrete_struct_clone"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let structs: [(Box<dyn Struct>, Box<dyn Struct>); 5] = [ let structs: [(Box<dyn Struct>, Box<dyn Struct>); 5] = [
( (
@ -193,23 +203,21 @@ fn concrete_struct_clone(criterion: &mut Criterion) {
BenchmarkId::new("NonGeneric", field_count), BenchmarkId::new("NonGeneric", field_count),
&standard, &standard,
|bencher, s| { |bencher, s| {
bencher.iter(|| black_box(s.clone_dynamic())); bencher.iter(|| s.clone_dynamic());
}, },
); );
group.bench_with_input( group.bench_with_input(
BenchmarkId::new("Generic", field_count), BenchmarkId::new("Generic", field_count),
&generic, &generic,
|bencher, s| { |bencher, s| {
bencher.iter(|| black_box(s.clone_dynamic())); bencher.iter(|| s.clone_dynamic());
}, },
); );
} }
} }
fn dynamic_struct_clone(criterion: &mut Criterion) { fn dynamic_struct_clone(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_struct_clone"); let mut group = create_group(criterion, bench!("dynamic_struct_clone"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let structs: [Box<dyn Struct>; 5] = [ let structs: [Box<dyn Struct>; 5] = [
Box::new(Struct1::default().clone_dynamic()), Box::new(Struct1::default().clone_dynamic()),
@ -226,16 +234,14 @@ fn dynamic_struct_clone(criterion: &mut Criterion) {
BenchmarkId::from_parameter(field_count), BenchmarkId::from_parameter(field_count),
&s, &s,
|bencher, s| { |bencher, s| {
bencher.iter(|| black_box(s.clone_dynamic())); bencher.iter(|| s.clone_dynamic());
}, },
); );
} }
} }
fn dynamic_struct_apply(criterion: &mut Criterion) { fn dynamic_struct_apply(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_struct_apply"); let mut group = create_group(criterion, bench!("dynamic_struct_apply"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
let patches: &[(fn() -> Box<dyn PartialReflect>, usize)] = &[ let patches: &[(fn() -> Box<dyn PartialReflect>, usize)] = &[
(|| Box::new(Struct16::default()), 16), (|| Box::new(Struct16::default()), 16),
@ -293,9 +299,7 @@ fn dynamic_struct_apply(criterion: &mut Criterion) {
} }
fn dynamic_struct_insert(criterion: &mut Criterion) { fn dynamic_struct_insert(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_struct_insert"); let mut group = create_group(criterion, bench!("dynamic_struct_insert"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
for field_count in SIZES { for field_count in SIZES {
group.throughput(Throughput::Elements(field_count as u64)); group.throughput(Throughput::Elements(field_count as u64));
@ -325,9 +329,7 @@ fn dynamic_struct_insert(criterion: &mut Criterion) {
} }
fn dynamic_struct_get_field(criterion: &mut Criterion) { fn dynamic_struct_get_field(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("dynamic_struct_get"); let mut group = create_group(criterion, bench!("dynamic_struct_get_field"));
group.warm_up_time(WARM_UP_TIME);
group.measurement_time(MEASUREMENT_TIME);
for field_count in SIZES { for field_count in SIZES {
group.throughput(Throughput::Elements(field_count as u64)); group.throughput(Throughput::Elements(field_count as u64));
@ -342,9 +344,7 @@ fn dynamic_struct_get_field(criterion: &mut Criterion) {
} }
let field = black_box("field_63"); let field = black_box("field_63");
bencher.iter(|| { bencher.iter(|| s.get_field::<()>(field));
black_box(s.get_field::<()>(field));
});
}, },
); );
} }

44
benches/src/lib.rs Normal file
View File

@ -0,0 +1,44 @@
/// Automatically generates the qualified name of a benchmark given its function name and module
/// path.
///
/// This macro takes a single string literal as input and returns a [`&'static str`](str). Its
/// result is determined at compile-time. If you need to create variations of a benchmark name
/// based on its input, use this in combination with [`BenchmarkId`](criterion::BenchmarkId).
///
/// # When to use this
///
/// Use this macro to name benchmarks that are not within a group and benchmark groups themselves.
/// You'll most commonly use this macro with:
///
/// - [`Criterion::bench_function()`](criterion::Criterion::bench_function)
/// - [`Criterion::bench_with_input()`](criterion::Criterion::bench_with_input)
/// - [`Criterion::benchmark_group()`](criterion::Criterion::benchmark_group)
///
/// You do not want to use this macro with
/// [`BenchmarkGroup::bench_function()`](criterion::BenchmarkGroup::bench_function) or
/// [`BenchmarkGroup::bench_with_input()`](criterion::BenchmarkGroup::bench_with_input), because
/// the group they are in already has the qualified path in it.
///
/// # Example
///
/// ```
/// mod ecs {
/// mod query {
/// use criterion::Criterion;
/// use benches::bench;
///
/// fn iter(c: &mut Criterion) {
/// // Benchmark name ends in `ecs::query::iter`.
/// c.bench_function(bench!("iter"), |b| {
/// // ...
/// });
/// }
/// }
/// }
/// ```
#[macro_export]
macro_rules! bench {
($name:literal) => {
concat!(module_path!(), "::", $name)
};
}