-
-
Notifications
You must be signed in to change notification settings - Fork 208
Physics Diagnostics #653
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
Merged
Merged
Physics Diagnostics #653
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Diagnostics are now stored in resources like `SolverDiagnostics` and `CollisionDiagnostics`. - Timers and counters are updated in the relevant systems, instead of wrapping every timed system with two other system. - Each plugins is responsible for setting up its own diagnostics, but a `PhysicsDiagnostic` trait and `App::register_physics_diagnostics` are used to abstract setup logic for `Diagnostics`. - Added a lot more diagnostics.
…d `bevy_diagnostic` feature
5 tasks
Member
Author
Jondolf
added a commit
that referenced
this pull request
Jun 4, 2025
# Objective As we continue working on performance improvements, having proper tools for benchmarking is increasingly important, both for measuring Avian against itself and for comparing it against other engines to see how they compare. #653 added runtime physics diagnostics, which is very valuable for seeing how long each part of the engine is taking while the app is running. However, we are still missing proper `cargo bench` -like tooling for running a suite of stress tests and getting relevant numerical results. `criterion` is not enough for this. We need tooling to: - run benchmarks that track the desired physics timers, not just total step times - run a benchmark with many different thread counts to demonstrate multi-threaded scaling - run a benchmark with different SIMD targets (AVX, SIMD128, NEON, SSE2, scalar) - ideally spit out some CSV file with the desired format to easily create plots for visualizing the benchmarks For the best control and flexibility, we need our own CLI tool for running physics benchmarks. ## Solution Add a `benches` package that contains both 2D and 3D benchmarks and a flexible CLI for running them. This replaces the old `avian2d/benches`, `avian3d/benches`, `benches_common_2d`, and `benches_common_3d` directories. The CLI supports the following options: ```text Options: -n, --name <NAME> The name or number of the benchmark to run. Leave empty to run all benchmarks -t, --threads <THREADS> A range for which thread counts to run the benchmarks with. Can be specified as `start..end` (exclusive), `start..=end` (inclusive), or `start` [default: 1..13] -s, --steps <STEPS> The number of steps to run for each benchmark [default: 300] -r, --repeat <REPEAT> The number of times to repeat each benchmark. The results will be averaged over these repetitions [default: 5] -l, --list List all available benchmarks in a numbered list -o, --output <OUTPUT> The output directory where results are written in CSV format. Leave empty to disable output -h, --help Print help -V, --version Print version ``` An example of running the "Large Pyramid 2D" benchmark with 1-6 threads and 500 steps, repeated 5 times: ```shell # Run with `benches` as the current working directory cargo run -- --name "Large Pyramid 2D" --threads 1..=6 --steps 500 --repeat 5 ``` The output might look something like this: ```text Running benchmark 'Large Pyramid 2D' with threads ranging from 1 to 6: | threads | avg time / step | min time / step | | ------- | --------------- | --------------- | | 1 | 12.29045 ms | 11.22260 ms | | 2 | 10.40321 ms | 9.27592 ms | | 3 | 9.65242 ms | 8.53754 ms | | 4 | 9.19108 ms | 8.15204 ms | | 5 | 9.03052 ms | 8.08754 ms | | 6 | 8.91510 ms | 7.87406 ms | ``` The CLI supports CSV output, producing files like the following: ```csv threads,step_ms 1,12.290453 2,10.403209 3,9.652423 4,9.191079 5,9.030517 6,8.915103 ``` Creating plots (e.g. just in Google Sheets or some other tool) is then very easy:  In the future, once we use wide SIMD for the solver, we will also add different SIMD targets to this. If we wanted to, we could also further automate plot creation by generating them directly as .png or .svg files in the CLI tool, but for now, manual authoring is fine. ## Benchmarks in Other Engines - Box2D has [similar tooling](https://github.com/erincatto/box2d/tree/main/benchmark) for benchmarks, and [posts results](https://box2d.org/files/benchmark_results.html) on their website. - Rapier also has [`benchmarks2d`](https://github.com/dimforge/rapier/tree/master/benchmarks2d) and [`benchmarks3d`](https://github.com/dimforge/rapier/tree/master/benchmarks3d) directories that use the Rapier testbed, which supports writing results to a CSV file.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
C-Feature
A new feature, making something new possible
C-Performance
Improvements or questions related to performance
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.

Objective
Fixes #564.
Expands significantly on #576.
For both benchmarking and optimizing Avian itself, and monitoring physics performance on the user side, it can be very useful to have timing information and various other statistics tracked by the physics engine. This is also done by engines such as Rapier and Box2D.
Solution
Summary:
CollisionDiagnosticsandSolverDiagnostics.bevy_diagnosticfeature andPhysicsDiagnosticsPluginfor optionally writing these diagnostics tobevy_diagnostic::DiagnosticsStore.diagnostic_uifeature andPhysicsDiagnosticsUiPluginfor optionally displaying physics diagnostics with a debug UI.Physics Diagnostics Resources
The natural place for diagnostics in Bevy would be
bevy_diagnostic::DiagnosticsStore. However:f64, which makes counters a bit more awkward.Thus, most diagnostics are tracked in separate resources such as
SolverDiagnostics:These are updated in the relevant systems. Timers should have a very small, fixed cost, so they are currently tracked by default and cannot be disabled (same as Box2D), aside from disabling e.g. the
SolverPluginitself. If it is deemed to have measurable overhead down the line, we could try putting timers behind a feature flag, though it does add some complexity.Integration With
DiagnosticsStoreIt can still be valuable to also record physics diagnostics to
bevy_diagnostic::DiagnosticsStoreto benefit from the history and smoothing functionality, and to monitor things from a single shared resource. This is supported by adding thePhysicsDiagnosticsPluginwith thebevy_diagnosticfeature enabled.There is some boilerplate required for registering the diagnostics, clearing the timers and counters, and writing them to the
DiagnosticsStore. To keep things more manageable, this has been abstracted with aPhysicsDiagnosticstrait,register_physics_diagnosticsmethod, andimpl_diagnostic_paths!macro.For the earlier
SolverDiagnostics, the implementation looks like this:The
SolverPlugincan then simply callapp.register_physics_diagnostics::<SolverDiagnostics>(), and everything should work automatically. The timers will only be written to theDiagnosticsStoreifPhysicsDiagnosticsPluginis enabled, keeping overhead small if the use ofDiagnosticsStoreis not needed.A nice benefit here is that each plugin is responsible for adding its own diagnostics, rather than there being a single place where all diagnostics are registered and stored. This is nice for modularity, and means that e.g.
SpatialQueryDiagnosticsare only added if theSpatialQueryPluginis added.Physics Diagnostics UI
Having all of these diagnostics available is nice, but viewing and displaying them in a useful way involves a decent amount of code and effort.
To make this easier (and prettier), an optional debug UI for displaying physics diagnostics is provided with the
diagnostic_uifeature andPhysicsDiagnosticsUiPlugin. It displays all active built-in physics diagnostics in neat groups, with both current and smoothed times shown.The visibility and settings of the UI can be configured using the
PhysicsDiagnosticsUiSettings.Example Improvements
The
ExampleCommonPluginhas been updated to replace the FPS counter with these physics diagnostics, and there are now text instructions to show what the different keys do.The diagnostics UI is hidden by default in the examples.
Differences to #576
#576 by @ptsd has an initial WIP diagnostics implementation with a simpler approach that more closely matches my original proposal in #564. It was a valuable base to build this PR on top of, but as I iterated on it, I ultimately went with quite a different approach.
That PR used a single
PhysicsDiagnosticsPluginthat set up all diagnostics manually. Timers were implemented by adding system sets before and after various parts of the simulation, and adding systems there to record spans, which were then written to theDiagnosticsStore.I didn't go with this approach, because:
Instead, I simply have each plugin define its own resource for its diagnostics where relevant. Timers are handled by tracking elapsed time inside systems with
bevy::utils::Instant(for wide platform support), and stored asDurations. Writing this information to theDiagnosticsStoreis optional.This is more modular, has less overhead, and works with substepping. It does add some complexity to the actual diagnostic management though, and makes diagnostics more spread out over the codebase, for better and for worse.