Skip to content

Conversation

froce
Copy link
Collaborator

@froce froce commented Oct 16, 2023

PR Details

I took another look at the profiler changes I made, as @manio143 mentioned a perf issue.
I misunderstood how Utf8JsonWriter works - it won't flush to the underlying stream automatically, so there were constant reallocations. And in GameProfilingSystem checking if it is time to update the output turned into a busy-wait while not subscribed to the Profiler.

Description

  • Fix busy-wait in GameProfilingSystem and make surrounding code clearer.
  • Add manual flushing to the JsonWriter, so the internal buffer doesn't grow indefinitely and put it on the fast path by skipping validation.

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My change requires a change to the documentation.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

…gProfileWriter

- Fix busy-wait in `GameProfilingSystem` and make surrounding code clearer.
- Add manual flushing to the JsonWriter, so the internal buffer doesn't grow indefinitely and put it on the fast path by skipping validation.
@manio143
Copy link
Member

GameProfilingSystem - Would it be possible to separate receiving messages and updating strings into two separate, concurrent loops?
With semaphore slim for synchronization.

I feel like there's a lot of complexity in reading the update method because it behaves one way in Fps mode, and differently in the other modes. containMarks can be made into a field, then we have two loops:

while (!cancellation) {
  await Delay(Refresh)
  await lock
  update strings
  release lock
}

await foreach (var event) {
  await lock
  update state with new event
  release lock
}

I don't think semaphore slim will add too much overhead on top of async here, but of course would be good to verify it doesn't change the perf much.

@froce
Copy link
Collaborator Author

froce commented Oct 18, 2023

Adding the semaphore adds about 80ns (~400ns -> ~480 ns) per event for me, I guess that would be okay?

Alternatively, how about splitting into one task for fps display and a second task for detailed stats and then changing the active task depending on the active state.

UpdateFpsStats:
while (!cancellation) {
  await Delay(Refresh)
  update strings if needed
}

UpdateDetailedStats:
while (!cancellation) {
  await foreach (var event) {
    update state with new event
    update strings if needed
  }
}

That way the complexity in the update method is also removed from the update method and we don't have to worry about synchronization. But I'm fine with your suggestion too, if you prefer it.

@manio143
Copy link
Member

I want it to be readable most of all (you're suggestion is ok).
Maybe having two tasks makes more sense and you can switch between them when switching the display type?

if (asyncUpdateTask != null && !asyncUpdateTask.IsCompleted)
{
stringBuilderTask.Wait();
asyncUpdateTask.Wait();
Copy link
Member

@Kryptos-FR Kryptos-FR Nov 19, 2023

Choose a reason for hiding this comment

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

Any long-running operation should have a TaskCancellationSource and pass the associated CancellationToken everywhere. Destroy() should not hang on the next RefreshTime tick.

if (eventReader != null)
{
Profiler.Unsubscribe(eventReader);
writerTask?.Wait();
Copy link
Member

Choose a reason for hiding this comment

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

Why does it need to wait after Stop() has been called. Can't it just signal a cancellation and return immediately?

Copy link
Member

Choose a reason for hiding this comment

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

I think it would be good to wait here in order to observe any exception which may have been thrown

Copy link
Member

Choose a reason for hiding this comment

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

This also need a CancellationTokenSource to stop the task at the earliest opportunity. There should also be a protection against calling Start() or Stop() multiple times on the same instance.

@froce
Copy link
Collaborator Author

froce commented Nov 25, 2023

Continued in #2050

@froce froce closed this Nov 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants