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

New implementation of PerformanceObserver and related APIs (fixes #45122) #45206

Open
wants to merge 58 commits into
base: main
Choose a base branch
from

Conversation

robik
Copy link
Contributor

@robik robik commented Jun 27, 2024

Summary:

Fix #45122.

performance.mark is currently O(1) but performance.clearMark is O(n) (being n the number of entries in the buffer), which makes this operation very slow.

Changes overview

  • Created new PerformanceEntryBuffer abstraction with the following subtypes, that differ on how entries are stored:
    • PerformanceEntryCircularBuffer - stores them in a ring buffer that was already implemented, removed key lookup cache (BoundedConsumableBuffer)
    • PerformanceEntryKeyedBuffer - stores them in a unordered_map, allowing for faster retrieval by type
    • PerformanceEntryLinearBuffer - a simple infinite buffer based on std::vector, currently used in a PerformanceObserver
  • Created PerformanceObserver abstraction on native side.
  • Created PerformanceObserverRegistry that collects active observers and forwards entries to observers that should retrieve them
  • Moved some method implementations to .cpp files to reduce potential compilation time slowdown. As the PerformanceEntryReporter can be included from anywhere in the code, it will be beneficial to make header files as light as possible.
  • Add comments to methods that note which standard is the method from/for.
  • Added some [[nodiscard]] attributes
  • Since the logic of routing entries to observers is moved to native side, JS side of the code got much simplified
  • If ever needed, PerformanceObserver can be created from native-side

Standards covered:

Changelog:

[GENERAL] [CHANGED] - Refactored performance-timeline module, which should improve performance of performance.clearMarks and performance.clearMeasures

Test Plan:

n/a

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 27, 2024
@robik robik force-pushed the robik/improve-performance-observer branch from 2ed73a9 to 5de7d48 Compare July 10, 2024 11:09
@robik
Copy link
Contributor Author

robik commented Jul 10, 2024

Pushed early changes for approach review. Still working on updating TurboModule and its spec to match new API.

@robik robik force-pushed the robik/improve-performance-observer branch from 5de7d48 to 7b45779 Compare July 24, 2024 14:11
@robik robik marked this pull request as ready for review July 24, 2024 14:11
@robik
Copy link
Contributor Author

robik commented Jul 24, 2024

Ready for an early review. Will undo changes related to exposing Performance TurboModule and still needs some testing from my side.

@facebook-github-bot facebook-github-bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jul 24, 2024
Copy link
Contributor

@rubennorte rubennorte left a comment

Choose a reason for hiding this comment

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

This is a larger refactor than I was expecting, but I like the direction we're taking here. I made an initial pass on the PR (please forgive the amount of comments, this is very large). Please let me know what you think, and thanks so much for this!

@@ -28,6 +30,14 @@ namespace facebook::react {
return std::make_shared<NativeIdleCallbacks>(jsInvoker);
}

if (name == NativePerformance::kModuleName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't want to enable this by default yet, until we've had a chance to test it thoroughly at Meta first. Please revert this for the PR (I understand you still need it to test it).

Copy link
Contributor

Choose a reason for hiding this comment

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

Please add the comment about this being temporary as well :P

@robik robik force-pushed the robik/improve-performance-observer branch 2 times, most recently from 844f334 to 8a77bcd Compare August 1, 2024 10:10
@robik
Copy link
Contributor Author

robik commented Aug 8, 2024

@rubennorte I've allowed myself to resolve comments that I think are corrected :) The remaining ones I still need some input.
Sorry for large batch of changes again, but I think I simplified a lot of things.

FYI: still not fully tested on my side 😅

@@ -28,6 +30,14 @@ namespace facebook::react {
return std::make_shared<NativeIdleCallbacks>(jsInvoker);
}

if (name == NativePerformance::kModuleName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add the comment about this being temporary as well :P

@robik robik force-pushed the robik/improve-performance-observer branch from caf3b65 to 8772490 Compare September 13, 2024 11:26
Comment on lines +24 to +25
double durationThreshold{DEFAULT_DURATION_THRESHOLD};
size_t droppedEntriesCount{0};
Copy link
Contributor

Choose a reason for hiding this comment

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

Do these need to be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It needs to be accessed from EntryReporter to validate if the entry should be added. I can make it protected, but then I'd need to make it a friend of EntryReporter which I'd avoid. As an alternative I could move the login into buffer itself, but that was just moved out of it :D

Copy link
Contributor

@rubennorte rubennorte Sep 18, 2024

Choose a reason for hiding this comment

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

I don't think we need durationThreshold here. We can just define it in the buffer for event entry types.

Regarding droppedEntriesCount, there's no reason to have them per buffer AFAIK, so we could make add return a boolean and then have a single count in the entry reporter, so you don't even have to iterate over each buffer. It's a bit simpler IMHO.

Actually, we need to keep them separate per buffer because the reported number is only for the entry types that the observer is observing. Please ignore this last comment :)

@robik robik force-pushed the robik/improve-performance-observer branch from e57a510 to 8fe46c2 Compare September 20, 2024 09:18
@facebook-github-bot
Copy link
Contributor

@rubennorte has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@@ -128,6 +137,18 @@ void NativePerformance::measure(
#endif
}

void NativePerformance::logEvent(
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't be exposed to JS because these are always emitted from native.

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 exposed them for unittests 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe make the name more explicit (e.g.: testOnly_logEvent).

double PerformanceObserver::getDroppedEntriesCount() noexcept {
double droppedEntriesCount = 0;

if (requiresDroppedEntries_) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a set of types that we need to report dropped entries. This should also work if we do this:

observer.observe({type: 'mark', buffered: true}); // report marks until this point and report dropped ones
observer.observer({type: measure', buffered: true}); // same, but for this time and until this moment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We iterate over observer types below, and the flag is reset after each observe. So it should work as mentioned (considering observe resets entries, not adds more)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we're not handling the timing of that correctly. We're determining the number of dropped entries when the callback is executed, not when we "dump" all the entries in the observer buffer. Also, if we execute the code I shared in previous example, we'd be dropping all the existing entries in the observer buffer because we just reassign it. We just append those and determine dropped entries at the time of observation.

@rubennorte rubennorte changed the title refactor: use map for user timing API for better performance (fixes #45122) New implementation of PerformanceObserver and related APIs (fixes #45122) Sep 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve performance of performance.clearMarks
3 participants