-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Allow systems using Diagnostics to run in parallel #8677
Allow systems using Diagnostics to run in parallel #8677
Conversation
To use Diagnostics a system was required to ask for a ResMut of the Diagnostics struct. This meant that any system writing a Diagnostic couldn't run in parallel (only a single mut instance). Instead, we can use channels to "send" the new metric without requiring mutability and then have a separate system that receives and integrates them at the end.
Would it be possible using the |
That seems like a nicer option if it works, I can test it out. I'm not sure about is how that will work with the |
@nicopap I'm not that familiar with Rather than have a system require A dummy implementation would be something like this (in actuality I would assume the structs would be merged with existing ones). #[derive(SystemParam)]
struct DiagnosticsParam<'s, F>
where
F: FnOnce() -> f64,
{
queue: Deferred<'s, DiagnosticsBuffer<F>>,
}
impl<'s, F> DiagnosticsParam<'s, F>
where
F: FnOnce() -> f64,
{
pub fn add_measurement(&mut self, id: DiagnosticId, value: F) {
self.queue.0.insert(id, value)
}
}
#[derive(Default)]
struct DiagnosticsBuffer<F: FnOnce() -> f64>(StableHashMap<DiagnosticId, F>);
impl<F> SystemBuffer for DiagnosticsBuffer<F>
where
F: FnOnce() -> f64,
{
fn apply(
&mut self,
system_meta: &bevy_ecs::system::SystemMeta,
world: &mut bevy_ecs::world::World,
) {
let mut diagnostics = world.resource_mut::<Diagnostics>();
for (id, value) in self.0.into_iter() {
diagnostics.add_measurement(id, value)
}
}
} This doesn't like the function pointer that is part of the |
You can get the edit: To be clear, you need to drop the |
Ok, I updated the PR to use a Deferred SystemParam and I verified it does run in parallel. The user experience has been hurt a little as now there are two structs they need to care about. The Once we are happy with the code the documentation will also need a brush up. @nicopap anything else I'm missing? |
Final update for now. To try and fix the "usability" I did a little renaming and added a new trait helper method. Now the API looks more like it used to for the write case, and only differs for the read. fn main() {
App::new()
...
.register_diagnostic(Diagnostic::new(SYSTEM_ITERATION_COUNT, "system_iteration_count", 10))
.add_systems(Update, my_system)
.run();
}
fn my_system(mut diagnostics: Diagnostics) {
// Add a measurement of 10.0 for our diagnostic each time this system runs.
diagnostics.add_measurement(SYSTEM_ITERATION_COUNT, || 10.0);
} To get this I renamed The Note that I added the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice work, just a small question on Diagnostic::add_measurement
pub fn add_measurement(&mut self, value: f64) { | ||
let time = Instant::now(); | ||
/// Add a new value as a [`DiagnosticMeasurement`]. | ||
pub fn add_measurement(&mut self, value: impl Into<DiagnosticMeasurement>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer if this took a DiagnosticMeasurement
over an Into<DiagnosticMeasurement>
. Or even be made private. It's breaking, but using this method directly is footgunny, so better hint through the type that you aren't supposed to add the measurement through this API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the Into<>
and just use the DiagnosticMeasurement
directly. I didn't make it private just because it seems like some people might be using the Diagnostic
struct without Diagnostics
and I erred on the side of reducing API changes. I'm happy to change it to private if preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I trust your judgment! I think It's fine to leave it public, especially with the new doc infos you added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! I'd like to see this merged.
Well documented, and a nice use of |
Objective
I was trying to add some
Diagnostics
to have a better break down of performance but I noticed that the current implementation uses aResMut
which forces the functions to all run sequentially whereas before they could run in parallel. This created too great a performance penalty to be usable.Solution
This PR reworks how the diagnostics work with a couple of breaking changes. The idea is to change how
Diagnostics
works by changing it to aSystemParam
. This allows us to hold aDeferred
buffer of measurements that can be applied later, avoiding the need for multiple mutable references to the hashmap. This means we can run systems that write diagnostic measurements in parallel.Firstly, we rename the old
Diagnostics
toDiagnosticsStore
. This clears up the original name for the new interface while allowing us to preserve more closely the original API.Then we create a new
Diagnostics
struct which implementsSystemParam
and contains a deferredSystemBuffer
. This can be used very similar to the oldDiagnostics
for writing new measurements.For reading the diagnostics, the user needs to change from
Diagnostics
toDiagnosticsStore
but otherwise the function calls are the same.Finally, we add a new method to the
App
for registering diagnostics. This replaces the old method of creating a startup system and adding it manually.Testing it, this PR does indeed allow Diagnostic systems to be run in parallel.
Changelog
Diagnostics
to implementSystemParam
which allows diagnostic systems to run in parallel.Migration Guide
Diagnostic
's using the newapp.register_diagnostic(Diagnostic::new(DIAGNOSTIC_ID, "diagnostic_name", 10));
mut diagnostics: ResMut<Diagnostics>
tomut diagnostics: Diagnostics
to allow the systems to run in parallel.diagnostics: Res<Diagnostics>
todiagnostics: Res<DiagnosticsStore>
.