bevyengine / bevyengine/bevy

Clean benchmarks storage type code duplication

Open
#5,161 1 comment 0 reactions 0 assignees View on GitHub
A-Diagnostics A-ECS C-Code-Quality S-Needs-Benchmarking
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## Objective

In ECS benchmarks, code is always duplicated to account for `Table` and `Sparse` storage types.

Duplication leads to difficulty in:
- Reviewing Pull Requests
- Maintaining exact duplication (changes in 1 codepath should be replicated exactly in the other)

## Work In Progress

- https://github.com/bevyengine/bevy/pull/4972 has a first implementation as a generic function.

## Possible solutions

### Generic function to help avoid the duplicated code.

See implementation

```rs
fn generic_bench(
bench_group: &mut BenchGroup,
mut benches: Vec>,
bench_parameters: P,
) {
for b in &mut benches {
b(bench_group, bench_parameters);
}
}

fn all_added_detection_generic(group: &mut BenchGroup, entity_count: u32) {
group.bench_function(
format!("{}_entities_{}", entity_count, std::any::type_name::()),
|bencher| {
bencher.iter_batched(
|| setup::(entity_count),
|mut world| {
let mut count = 0;
let mut query = world.query_filtered::>();
for entity in query.iter(&world) {
black_box(entity);
count += 1;
}
assert_eq!(entity_count, count);
},
criterion::BatchSize::LargeInput,
);
},
);
}

fn all_added_detection(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("all_added_detection");
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
generic_bench(
&mut group,
vec![
Box::new(all_added_detection_generic::),
Box::new(all_added_detection_generic::),
],
entity_count,
);
}
}
```

### Declarative macro

It was my first attempt but macros are harder to read for most people, less standard and this solution would need more work to be as flexible.

See implementation

```rs
#[macro_export]
macro_rules! bevy_bench {
( $( $bench:ident, $harness:ident),+ ) => {
$(
fn $harness(criterion: &mut Criterion) {
let mut group: criterion::BenchmarkGroup =
criterion.benchmark_group(stringify!($bench));
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));

for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
$bench::(&mut group, entity_count);
$bench::(&mut group, entity_count);
}

group.finish();
}
)+
};
}

fn all_added_detection_generic(group: &mut BenchGroup, entity_count: u32) {
group.bench_function(
format!("{}_entities_{}", entity_count, std::any::type_name::()),
|bencher| {
bencher.iter_batched(
|| setup::(entity_count),
|mut world| {
let mut count = 0;
let mut query = world.query_filtered::>();
for entity in query.iter(&world) {
black_box(entity);
count += 1;
}
assert_eq!(entity_count, count);
},
criterion::BatchSize::LargeInput,
);
},
);
}

bevy_bench!(all_added_detection_generic, all_added_detection);
```

### procedural macro

As those are more complex I think another solution is better until proven otherwise.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.