bevy_reflect: Function reflection benchmarks (#14647)

# Objective

It would be good to have benchmarks handy for function reflection as it
continues to be worked on.

## Solution

Add some basic benchmarks for function reflection.

## Testing

To test locally, run the following in the `benches` directory:

```
cargo bench --bench reflect_function
```

## Results

Here are a couple of the results (M1 Max MacBook Pro):

<img width="936" alt="Results of benching calling functions vs closures
via reflection. Closures average about 40ns, while functions average
about 55ns"
src="https://github.com/user-attachments/assets/b9a6c585-5fbe-43db-9a7b-f57dbd3815e3">
<img width="936" alt="Results of benching converting functions vs
closures into their dynamic representations. Closures average about
34ns, while functions average about 37ns"
src="https://github.com/user-attachments/assets/4614560a-7192-4c1e-9ade-7bc5a4ca68e3">

Currently, it seems `DynamicClosure` is just a bit more performant. This
is likely due to the fact that `DynamicFunction` stores its function
object in an `Arc` instead of a `Box` so that it can be `Send + Sync`
(and also `Clone`).

We'll likely need to do the same for `DynamicClosure` so I suspect these
results to change in the near future.
This commit is contained in:
Gino Valente 2024-08-10 20:02:06 -07:00 committed by GitHub
parent 3e10fd8534
commit 91fa4bb649
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 1 deletions

View file

@ -15,7 +15,7 @@ bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] }
bevy_hierarchy = { path = "../crates/bevy_hierarchy" }
bevy_internal = { path = "../crates/bevy_internal" }
bevy_math = { path = "../crates/bevy_math" }
bevy_reflect = { path = "../crates/bevy_reflect" }
bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] }
bevy_render = { path = "../crates/bevy_render" }
bevy_tasks = { path = "../crates/bevy_tasks" }
bevy_utils = { path = "../crates/bevy_utils" }
@ -34,6 +34,11 @@ name = "ecs"
path = "benches/bevy_ecs/benches.rs"
harness = false
[[bench]]
name = "reflect_function"
path = "benches/bevy_reflect/function.rs"
harness = false
[[bench]]
name = "reflect_list"
path = "benches/bevy_reflect/list.rs"

View file

@ -0,0 +1,62 @@
use bevy_reflect::func::{ArgList, IntoClosure, TypedFunction};
use bevy_reflect::prelude::*;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
criterion_group!(benches, typed, into, call, clone);
criterion_main!(benches);
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn typed(c: &mut Criterion) {
c.benchmark_group("typed")
.bench_function("function", |b| {
b.iter(|| add.get_function_info());
})
.bench_function("closure", |b| {
let capture = 25;
let closure = |a: i32| a + capture;
b.iter(|| closure.get_function_info());
});
}
fn into(c: &mut Criterion) {
c.benchmark_group("into")
.bench_function("function", |b| {
b.iter(|| add.into_function());
})
.bench_function("closure", |b| {
let capture = 25;
let closure = |a: i32| a + capture;
b.iter(|| closure.into_closure());
});
}
fn call(c: &mut Criterion) {
c.benchmark_group("call")
.bench_function("function", |b| {
let add = add.into_function();
b.iter_batched(
|| ArgList::new().push_owned(75_i32).push_owned(25_i32),
|args| add.call(args),
BatchSize::SmallInput,
);
})
.bench_function("closure", |b| {
let capture = 25;
let add = (|a: i32| a + capture).into_closure();
b.iter_batched(
|| ArgList::new().push_owned(75_i32),
|args| add.call(args),
BatchSize::SmallInput,
);
});
}
fn clone(c: &mut Criterion) {
c.benchmark_group("clone").bench_function("function", |b| {
let add = add.into_function();
b.iter(|| add.clone());
});
}