Skip to content

Commit

Permalink
New benchmarks (#1)
Browse files Browse the repository at this point in the history
* feat: create custom benchmarks

* feat: update github actions for new benchmarks

* fix: checkout repository first

* fix: don't optimize out `world.get_entity`

* chore: add comments

* fix: always use latest bencher version

* feat: update bencher command

* chore: final comments
  • Loading branch information
BD103 committed Jun 9, 2024
1 parent 858c4c7 commit 28c506d
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 13 deletions.
24 changes: 11 additions & 13 deletions .github/workflows/daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,36 @@ env:

jobs:
bench:
name: Bench
name: Run benchmarks
runs-on: ubuntu-latest
env:
BENCHER_PROJECT: bevy
steps:
- name: Checkout Bevy
- name: Checkout repository
uses: actions/checkout@v4
with:
repository: bevyengine/bevy

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache build files
uses: Leafwing-Studios/cargo-cache@v1
with:
cargo-target-dir: benches/target

- name: Install Bencher
uses: bencherdev/bencher@v0.4.10
uses: bencherdev/bencher@main

# Run benchmarks, piping output to both `results.txt` and stdout.
- name: Run benchmarks
working-directory: benches
run: cargo bench -- 2>&1 | tee results.txt

- name: Upload benchmarks
env:
BENCHER_PROJECT: bevy
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
BENCHER_TESTBED: github-actions
# --no-hash: Do not use the Git hash, because we care about the Bevy commit and not our
# own.
# --file: Upload saved Criterion output instead of running a command.
run: |
bencher run \
--branch main \
--testbed github-actions \
--adapter rust_criterion \
--no-hash \
--err \
--file benches/results.txt
--file results.txt
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Rust
/target
Cargo.lock

# Used for local testing
.env
18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "bevy-bencher"
edition = "2021"
publish = false

[dev-dependencies]
# Benchmarking library.
criterion = "0.5.1"

# Seedable PRNG
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }

# Bevy, on the latest commit.
bevy_ecs = { git = "https://github.com/bevyengine/bevy.git" }

[[bench]]
name = "main"
harness = false
61 changes: 61 additions & 0 deletions benches/ecs/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
mod types;

use self::types::*;
use bevy_ecs::prelude::*;
use criterion::{criterion_group, BatchSize, Criterion};
use rand::prelude::*;
use std::{hint::black_box, iter::repeat};

/// Benchmarks spawning an entity into a [`World`].
pub fn world_spawn(c: &mut Criterion) {
c.bench_function("world_spawn", |b| {
let mut world = World::new();

b.iter(|| {
world.spawn((A(0), B(0)));
});
});
}

/// Benchmarks iterating over all matching entities within a [`World`].
pub fn world_query_iter(c: &mut Criterion) {
c.bench_function("world_query_iter", |b| {
let mut world = World::new();

// Spawn some with `A`, some with `A` and `B`, and some without `A`.
world.spawn_batch(repeat((A(0), B(0))).take(64));
world.spawn_batch(repeat(A(0)).take(64));
world.spawn_batch(repeat(B(0)).take(64));

b.iter(|| {
for a in world.query::<&A>().iter(&world) {
// Pretend we're doing something with `a` so this is not optimized away.
black_box(a);
}
});
});
}

pub fn world_get_entity(c: &mut Criterion) {
c.bench_function("world_get_entity", |b| {
let mut world = World::new();
let mut prng = crate::create_prng();

// Spawn 64 entities and store their IDs in a list.
let ids: Vec<Entity> = world.spawn_batch(repeat((A(0), B(0))).take(64)).collect();

b.iter_batched(
// Pick a random ID from the list, outside of the benchmark.
|| ids.choose(&mut prng).unwrap(),
|&input| {
// Get the entity from the world. The input is random to hopefully escape any
// locality optimizations. We use `.get_entity()` instead of `.entity()` to avoid
// an extra `.unwrap()` call, though the result will never be `None`.
black_box(world.get_entity(input));
},
BatchSize::SmallInput,
);
});
}

criterion_group!(group, world_spawn, world_query_iter, world_get_entity);
13 changes: 13 additions & 0 deletions benches/ecs/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use bevy_ecs::prelude::*;

/// A 32-bit wide component.
///
/// This is intended to not match with [`B`]'s size, to force the ECS to deal with padding.
#[derive(Component, Clone)]
pub struct A(pub u32);

/// A 16-bit wide component.
///
/// This is intended to not match with [`A`]'s size, to force the ECS to deal with padding.
#[derive(Component, Clone)]
pub struct B(pub u16);
13 changes: 13 additions & 0 deletions benches/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mod ecs;

use criterion::criterion_main;
use rand::{prelude::*, rngs::SmallRng};

pub(crate) fn create_prng() -> impl Rng {
// A small and fast psuedo-random number generator. This is not reproducible across 32-bit
// and 64-bit platforms, but that shouldn't be a problem here. The seed was chosen somewhat
// randomly, but modified to have a nice distribution of 1s and 0s.
SmallRng::seed_from_u64(0x7df09deb486e920a)
}

criterion_main!(ecs::group);
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Nothing here, check out the benches folder!

0 comments on commit 28c506d

Please sign in to comment.