Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added initial benchmarks #551

Merged
merged 18 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
env:
CARGO_TERM_COLOR: always
RUST_CACHE_KEY: rust-cache-20240701
CARGO_PROFILE_DEV_DEBUG: none

jobs:
check-fmt:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["bevy_rapier2d", "bevy_rapier3d"]
members = ["bevy_rapier2d", "bevy_rapier3d", "custom_benches"]
resolver = "2"

[profile.dev]
Expand Down
10 changes: 10 additions & 0 deletions benches_common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "benches_common"
version = "0.1.0"
license = "Apache-2.0"
edition = "2021"

[dependencies]
bevy = { version = "0.14.0-rc.3", default-features = false }
bevy_rapier3d = { version = "0.27.0-rc.1", path = "../bevy_rapier3d", default-features = false }
divan = "0.1"
63 changes: 63 additions & 0 deletions benches_common/src/lib.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of benches_common be its own create, it should just be a subdirectory in the bevy_rapier3d/benches folder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the same hierarchy of avian : https://github.com/Jondolf/avian/tree/main/crates ; I'd have preferred a way to enable feature on benches_common to support either 2d or 3d, but I didn't try that.

Either way, it's not too much code, and we can probably focus on 3d for now.

Copy link
Contributor Author

@Vrixyz Vrixyz Jul 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That actually allows me to share some logic with the custom benchmark.

I did add this logic in the custom benchmark, and call into this custom benchmark library from the "simpler" benches.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use bevy::{
app::PluginsState,
prelude::*,
render::{
settings::{RenderCreation, WgpuSettings},
RenderPlugin,
},
scene::ScenePlugin,
time::TimeUpdateStrategy,
};
use bevy_rapier3d::prelude::*;

pub fn default_app() -> App {
let mut app = App::new();

app.add_plugins((
WindowPlugin::default(),
MinimalPlugins,
AssetPlugin::default(),
ScenePlugin,
RenderPlugin {
render_creation: RenderCreation::Automatic(WgpuSettings {
backends: None,
..Default::default()
}),
..Default::default()
},
ImagePlugin::default(),
HierarchyPlugin,
TransformPlugin,
RapierPhysicsPlugin::<()>::default(),
));

// 60 physics
app.insert_resource(TimeUpdateStrategy::ManualDuration(
std::time::Duration::from_secs_f32(1f32 / 60f32),
));
app
}

pub fn wait_app_start(app: &mut App) {
while app.plugins_state() != PluginsState::Ready {
bevy::tasks::tick_global_task_pools_on_main_thread();
}

app.finish();
app.cleanup();
}

pub fn bench_app(bencher: divan::Bencher, steps: u32, setup: impl Fn(&mut App)) {
bencher
.with_inputs(|| {
let mut app = default_app();
setup(&mut app);
wait_app_start(&mut app);
app
})
.bench_local_values(|mut app| {
for _ in 0..steps {
app.update();
}
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
});
}
10 changes: 10 additions & 0 deletions bevy_rapier3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,24 @@ log = "0.4"
serde = { version = "1", features = ["derive"], optional = true }

[dev-dependencies]
benches_common = { version = "0.1", path = "../benches_common" }
bevy = { version = "0.14.0-rc.3", default-features = false, features = [
"x11",
"tonemapping_luts",
"bevy_state",
] }
approx = "0.5.1"
glam = { version = "0.27", features = ["approx"] }
divan = "0.1"

[package.metadata.docs.rs]
# Enable all the features when building the docs on docs.rs
features = ["debug-render-3d", "serde-serialize"]

[[bench]]
name = "cubes"
harness = false

[[bench]]
name = "many_pyramids3"
harness = false
43 changes: 43 additions & 0 deletions bevy_rapier3d/benches/cubes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Translated from avian benchmark.
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved

use benches_common::bench_app;
use bevy::prelude::*;
use bevy_rapier3d::math::*;
use bevy_rapier3d::prelude::*;

fn setup_cubes(app: &mut App, size: u32) {
app.add_systems(Startup, move |mut commands: Commands| {
commands.spawn((
RigidBody::Fixed,
Transform::from_translation(-2.0 * Vect::Z),
Collider::cuboid(100.0, 1.0, 100.0),
));
for x in 0..size {
for z in 0..size {
commands.spawn((
RigidBody::Dynamic,
Transform::from_translation(Vec3::new(x as f32, 2.0, z as f32)),
Collider::cuboid(1.0, 1.0, 1.0),
));
}
}
});
}

#[divan::bench]
fn cubes_3x3_30_steps(bencher: divan::Bencher) {
bench_app(bencher, 30, |app| setup_cubes(app, 3))
}
#[divan::bench]
fn cubes_5x5_30_steps(bencher: divan::Bencher) {
bench_app(bencher, 30, |app| setup_cubes(app, 5))
}
#[divan::bench]
fn cubes_10x10_30_steps(bencher: divan::Bencher) {
bench_app(bencher, 30, |app| setup_cubes(app, 10))
}

fn main() {
// Run registered benchmarks.
divan::main();
}
86 changes: 86 additions & 0 deletions bevy_rapier3d/benches/many_pyramids3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//! Translated from rapier benchmark.
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved

use benches_common::bench_app;
use bevy::prelude::*;
use bevy_rapier3d::math::*;
use bevy_rapier3d::prelude::*;

pub fn create_pyramid(commands: &mut Commands, offset: Vect, stack_height: usize, rad: f32) {
let shift = rad * 2.0;

for i in 0usize..stack_height {
for j in i..stack_height {
let fj = j as f32;
let fi = i as f32;
let x = (fi * shift / 2.0) + (fj - fi) * shift;
let y = fi * shift;

// Build the rigid body.
commands.spawn((
RigidBody::Dynamic,
Transform::from_translation(Vec3::new(x, y, 0.0) + offset),
Collider::cuboid(1.0, 1.0, 1.0),
));
}
}
}

pub fn setup_cubes(app: &mut App, pyramid_count: usize, stack_height: usize) {
app.add_systems(Startup, move |mut commands: Commands| {
let rad = 0.5;
let spacing = 4.0;

/*
* Ground
*/
let ground_size = 50.0;
let ground_height = 0.1;

commands.spawn((
RigidBody::Fixed,
Transform::from_translation(Vect::new(0.0, -ground_height, 0.0)),
Collider::cuboid(
ground_size,
ground_height,
pyramid_count as f32 * spacing / 2.0 + ground_size,
),
));

/*
* Create the cubes
*/
for pyramid_index in 0..pyramid_count {
let bottomy = rad;
create_pyramid(
&mut commands,
Vect::new(
0.0,
bottomy,
(pyramid_index as f32 - pyramid_count as f32 / 2.0) * spacing,
),
stack_height,
rad,
);
}
});
}

#[divan::bench(sample_count = 5, sample_size = 5)]
fn pyramid_1_with_height_2(bencher: divan::Bencher) {
bench_app(bencher, 1000, |app| setup_cubes(app, 1, 2));
}

#[divan::bench(sample_count = 2, sample_size = 2)]
fn pyramid_1_with_height_20(bencher: divan::Bencher) {
bench_app(bencher, 100, |app| setup_cubes(app, 1, 20));
}

#[divan::bench(sample_count = 1, sample_size = 1)]
fn pyramid_2_with_height_20(bencher: divan::Bencher) {
bench_app(bencher, 100, |app| setup_cubes(app, 2, 20));
}

fn main() {
// Run registered benchmarks.
divan::main();
}
15 changes: 15 additions & 0 deletions custom_benches/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "custom_benches"
version = "0.1.0"
description = "Custom benchmarks for bevy_rapier."
readme = "./README.md"
license = "Apache-2.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rapier3d = { features = ["profiler"], version = "0.21" }
bevy_rapier3d = { version = "0.27.0-rc.1", path = "../bevy_rapier3d" }
bevy = { version = "0.14.0-rc.3", default-features = false }
benches_common = { version = "0.1", path = "../benches_common" }
18 changes: 18 additions & 0 deletions custom_benches/README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cargo bench section should be removed to the last position in this file. The Custom benches section should be a bit more wordy by saying that it runs benchmarks manually, without bench harnesses, to obtain more detailed results on long-running simulations.

The cargo bench section should say something like "For short-lived benchmarks based on statistical analysis, benchmarks can be run through the divan bench harness.`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side note: I imagine we could use the bench approach (with our own custom bench harness) for better control over custom benchmarks, i.e. run specific custom benches, but I'd need to research that a bit, that can probably be a follow up.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

## cargo bench

```sh
cargo bench -p bevy_rapier3d
```

## Custom benches

```sh
cargo run --release -p custom_benches
```

## Flamegraph

```sh
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph
```
112 changes: 112 additions & 0 deletions custom_benches/src/main.rs
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Translated from rapier benchmark.
//!
use benches_common::default_app;
use benches_common::wait_app_start;
use bevy::prelude::*;
use bevy_rapier3d::dynamics::RigidBody;
use bevy_rapier3d::geometry::Collider;
use bevy_rapier3d::math::Vect;
use bevy_rapier3d::plugin::RapierContext;

pub fn create_pyramid(commands: &mut Commands, offset: Vect, stack_height: usize, rad: f32) {
let shift = rad * 2.0;

for i in 0usize..stack_height {
for j in i..stack_height {
let fj = j as f32;
let fi = i as f32;
let x = (fi * shift / 2.0) + (fj - fi) * shift;
let y = fi * shift;

// Build the rigid body.
commands.spawn((
RigidBody::Dynamic,
Transform::from_translation(Vec3::new(x, y, 0.0) + offset),
Collider::cuboid(1.0, 1.0, 1.0),
));
}
}
}

pub fn setup_cubes(app: &mut App, pyramid_count: usize, stack_height: usize) {
app.add_systems(Startup, move |mut commands: Commands| {
let rad = 0.5;
let spacing = 4.0;

/*
* Ground
*/
let ground_size = 50.0;
let ground_height = 0.1;

commands.spawn((
RigidBody::Fixed,
Transform::from_translation(Vect::new(0.0, -ground_height, 0.0)),
Collider::cuboid(
ground_size,
ground_height,
pyramid_count as f32 * spacing / 2.0 + ground_size,
),
));

/*
* Create the cubes
*/
for pyramid_index in 0..pyramid_count {
let bottomy = rad;
create_pyramid(
&mut commands,
Vect::new(
0.0,
bottomy,
(pyramid_index as f32 - pyramid_count as f32 / 2.0) * spacing,
),
stack_height,
rad,
);
}
});
}

pub fn custom_bencher(steps: usize, setup: impl Fn(&mut App)) {
let mut app = default_app();
setup(&mut app);
wait_app_start(&mut app);

let mut timer_total = rapier3d::counters::Timer::new();
let mut timer_full_update = rapier3d::counters::Timer::new();
let mut rapier_step_times = vec![];
let mut total_update_times = vec![];
timer_total.start();
for _ in 0..steps {
timer_full_update.start();
app.update();
timer_full_update.pause();
let elapsed_time = timer_full_update.time() as f32;
let rc = app.world().resource::<RapierContext>();
rapier_step_times.push(rc.pipeline.counters.step_time.time() as f32);
total_update_times.push(elapsed_time);
}
timer_total.pause();
let average_total = total_update_times.iter().sum::<f32>() / total_update_times.len() as f32;
println!("average total time: {}", average_total);
let average_rapier_step =
rapier_step_times.iter().sum::<f32>() / rapier_step_times.len() as f32;
println!("average rapier step time: {}", average_rapier_step);
let average_rapier_overhead = average_total - average_rapier_step;
println!("average bevy overhead: {}", average_rapier_overhead);
println!("total time: {}", timer_total.time());
}

fn pyramid_1_with_height_2() {
custom_bencher(1000, |app| setup_cubes(app, 1, 2));
}

fn pyramid_2_with_height_20() {
custom_bencher(100, |app| setup_cubes(app, 3, 20));
}

fn main() {
pyramid_1_with_height_2();
pyramid_2_with_height_20();
}