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

[Performance] Onyx usage in withLocalize adds a significant overhead #4268

Closed
kidroca opened this issue Jul 28, 2021 · 16 comments · Fixed by #4253
Closed

[Performance] Onyx usage in withLocalize adds a significant overhead #4268

kidroca opened this issue Jul 28, 2021 · 16 comments · Fixed by #4253
Assignees
Labels
Daily KSv2 Engineering External Added to denote the issue can be worked on by a contributor

Comments

@kidroca
Copy link
Contributor

kidroca commented Jul 28, 2021

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


What performance issue do we need to solve?

e.g. memory consumption, storage read/write times, React native bridge concerns, inefficient React component rendering, etc.

  • storage read/write times
  • inefficient React component rendering

This component is one of the most used components and has more than 310 instances depending on chat size
The current way that the HOC works is that it will make a new Onyx connection per each withOnyx instance.
A benchmark reviewed that for a large chat the we're spending about 50sec for the Onyx.get related to retrieving the preferredLocale

What is the impact of this on end-users?

List specific user experiences that will be improved by solving this problem e.g. app boot time, time to for some interaction to complete, etc.

  • app boot time
  • chat switching time

List any benchmarks that show the severity of the issue

Please also provide exact steps taken to collect metrics above if any so we can independently verify the results.

I'm using Onyx.printMetrics() to monitor the Onyx.get calls that happen during init: https://github.com/Expensify/react-native-onyx#benchmarks

Results

Device Samsung Galaxy S7 Edge (2016)
OS Android 8.0
Total: 123.80sec     Last 17.49sec     ReportID 71955477
method total time spent max min avg time last call completed calls made
Onyx:get 96.85sec 2.92sec 0.10ms 144.77ms 17.48sec 669

Setup

Using this Expensify/App hash: b2582f3
Modify this file: src/Expensify.js

  • For Onyx.init add captureMetrics: true
  • At the end of the file add global.Onyx = Onyx;

Modify src/libs/Navigation/AppNavigator/MainDrawerNavigator.js

  • Make getInitialReportScreenParams always return the same report
  • e.g. return {reportID: '71955477'};

Start the app and connect with Flipper or a Hermes Debugger

Steps

  1. Reload the app
  2. Wait for processes to settle (about 5-10sec after LHN loads)
  • you can use Onyx.printMetrics({ format: 'string' }}) when it starts to return the same value - everything is loaded
  1. Run Onyx.printMetrics({ format: 'csv', raw: true })
  2. Copy the results to a temp file
  3. Import file as new sheet in excel

Proposed solution

Extract a LocaleContextProvider that wraps the App - the implementation would be pretty much identical to how the class WithLocalize works at the moment:

class WithLocalize extends React.Component {

The difference would be that it will render a React ContextProvider component
Now only the top level component that wraps the App subscribes to Onyx
The HOC withLocalize changes to use a LocaleContextConsumer
It uses the context to get translation related props and pass them to the WrappedComponent
Everything else stays the same

List any benchmarks after implementing the changes to show impacts of the proposed solution (if any)

Note: These should be the same as the benchmarks collected before any changes.
I've done a quick test to verify there will be a significant reduction in Onyx.get time. It's demonstrated in the initial changes in the PR here: #4253

Total: 62.62sec     Last 12.27sec     ReportID 71955477
method total time spent max min avg time last call completed calls made
Onyx:get 40.44sec 1.79sec 0.08ms 139.94ms 12.27sec 289

Platform:

Where is this issue occurring?

  • Web
  • iOS
  • Android (Biggest impact)
  • Desktop App
  • Mobile Web

Version Number: 1.0.80-2
Logs: https://stackoverflow.com/c/expensify/questions/4856
Notes/Photos/Videos: Any additional supporting documentation
Expensify/Expensify Issue URL:

View all open jobs on Upwork

@MelvinBot
Copy link

Triggered auto assignment to @mountiny (Engineering), see https://stackoverflow.com/c/expensify/questions/4319 for more details.

@kidroca
Copy link
Contributor Author

kidroca commented Jul 28, 2021

Issue created per the discussion here: #4253 (comment)

@mountiny
Copy link
Contributor

@kidroca 👋 Since this is a daily issue, can you try to drop some line here every working day to update on your progress. Either some summary of what has been done in the Draft PR (it helps to see the steps made in a bigger picture) or simply that you have not had time to work on this that given day.

Thank you very much and have a great day 🙌

@kidroca
Copy link
Contributor Author

kidroca commented Jul 29, 2021

@mountiny
OK will do.
I wasn't sure I should be working on this ticket before someone officially approved it
Also ATM the ticket is not assigned to me, can you add me to the assignees?

@mountiny mountiny added the External Added to denote the issue can be worked on by a contributor label Jul 29, 2021
@MelvinBot
Copy link

Triggered auto assignment to @JmillsExpensify (External), see https://stackoverflow.com/c/expensify/questions/8582 for more details.

@mountiny
Copy link
Contributor

mountiny commented Jul 29, 2021

@kidroca Feel free to start working on this. Performance is very important to us right now. Jason should be able to create the Upwork job for you 🙌

Let me know if you will need help with anything!

@kidroca
Copy link
Contributor Author

kidroca commented Jul 30, 2021

Thanks, I just needed the green light
Instead of creating an Upwork job I think I can just log my work for this ticket under my hourly contract

cc @arielgreen

@mountiny
Copy link
Contributor

@kidroca Ah, you are right, I was not aware of this. I believe that is the way to go. Is that right, Ariel? :) Thank you for confirming!

@arielgreen
Copy link
Contributor

Yep, @kidroca, that's perfect. Thanks.

@kidroca
Copy link
Contributor Author

kidroca commented Jul 30, 2021

I've pulled the latest changes and captured new "PRE" benchmark information.

hash: f856107

Legend

  • Total: sum of all calls made by Onyx
  • Last call ended at: end time of the last call made by Onyx (often this is not a "get" call, but a "set")
  • Last long Onyx#get at: the last time a get call took more than 100ms to complete
  • Get "currencyList" at: The time Onyx.get is called with the currencyList key. Used as a reference point as usually it's the point on Android where the "last long" call happens.
  • Onyx#get: Total time spent in Onyx.get calls

Android Before

  Run1 Run2 Run3 Run4 Run5 Run6 Run7
Total 64.08sec 68.33sec 63.63sec 63.82sec 64.24sec 61.25sec 68.40sec
Last call ended at 11.72sec 11.34sec 11.43sec 11.58sec 11.43sec 11.22sec 11.49sec
Last long get at 4.62sec 4.60sec 5.08sec 4.77sec 4.75sec 4.57sec 4.65sec
Get "currencyList" at 4.62sec 4.60sec 5.08sec 4.77sec 4.75sec 4.57sec 4.65sec
Onyx#get 44.11sec 46.13sec 44.94sec 44.27sec 44.58sec 42.47sec 46.88sec

Android After

  Run1 Run2 Run3 Run4 Run5 Run6 Run7
Total 58.14sec 54.90sec 57.63sec 55.32sec 58.14sec 58.61sec 55.75sec
Last call ended at 11.05sec 10.87sec 11.06sec 11.09sec 11.05sec 10.90sec 11.11sec
Last long get at 4.09sec 4.11sec 4.15sec 4.17sec 4.09sec 4.17sec 4.18sec
Get "currencyList" at 4.09sec 4.11sec 4.55sec 4.17sec 4.09sec 4.17sec 4.18sec
Onyx#get 39.34sec 37.90sec 40.40sec 37.47sec 39.34sec 40.31sec 37.68sec

iOS Before

  Run1 Run2 Run3 Run4 Run5 Run6 Run7
Total 28.85sec 29.80sec 28.45sec 30.11sec 33.27sec 29.03sec 29.62sec
Last call ended at 13.48sec 8.97sec 8.98sec 8.99sec 9.01sec 9.02sec 8.99sec
Last long get at 2.40sec 2.24sec 2.08sec 2.29sec 2.10sec 2.12sec 2.28sec
Get "currencyList" at 2.40sec 2.83sec 2.92sec 2.39sec 2.10sec 2.11sec 2.54sec
Onyx#get 17.37sec 17.89sec 17.28sec 18.51sec 19.62sec 17.58sec 17.98sec

iOS After

  Run1 Run2 Run3 Run4 Run5 Run6 Run7
Total 21.68sec 23.12sec 23.22sec 23.06sec 24.28sec 23.39sec 23.33sec
Last call ended at 8.72sec 8.46sec 8.42sec 8.48sec 8.48sec 8.44sec 8.52sec
Last long get at 2.10sec 1.68sec 1.41sec 1.52sec 1.66sec 1.52sec 1.51sec
Get "currencyList" at 1.68sec 2.03sec 2.01sec 1.88sec 1.83sec 1.90sec 1.89sec
Onyx#get 11.72sec 13.76sec 13.95sec 13.94sec 13.77sec 13.92sec 13.99sec

Edit: Added "After" benchmarks and reordered for easier comparison

@kidroca
Copy link
Contributor Author

kidroca commented Jul 30, 2021

Added the new "After" benchmarks as well
It seems whatever recent improvements were merged to main they reduced the Onyx get calls.
My original benchmark on this same chat had 669 Onyx.get calls, now they are 430 (before the proposed change).
With the proposed change the calls are reduced to 297.
The "Total Time" isn't reduced as significantly as the last hash (123 -> 63) but it's still reduced (65 -> 56)
I think we're seeing the effect of the RN bridge getting "un-flooded" and so the drop in time is not as big as before
because the bridge doesn't have to delay execution

I've benchmarked init I haven't captured how chat switch is affected. I can do another run to capture this next week

@quinthar
Copy link
Contributor

Out of curiosity, is this the same benchmark for iOS and Android? Is it safe to say Android is roughly 2x slower overall? What is the major cause of the 2x slowdown?

@kidroca
Copy link
Contributor Author

kidroca commented Jul 31, 2021

Yes, it's the same test for both platforms
These are physical devices

  1. my iOS is much newer - 11 Pro, while the Android is older 2016 Samsung Galaxy S7. On emulators the times are closer, though iOS still wins
  2. Native implementation - the implementation in Java is not necessarily the same as in Objective C or swift
  3. Javascript Engine - I've heard the JS engine used on iOS is faster

@quinthar
Copy link
Contributor

quinthar commented Jul 31, 2021 via email

@marcaaron
Copy link
Contributor

something, that is making up the bulk of the performance difference between the two platforms that are in emulation.

As far as emulation goes, my understanding is that iOS will be faster because Xcode compiles for x86 and Android Studio emulates ARM.

@quinthar
Copy link
Contributor

quinthar commented Aug 2, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Daily KSv2 Engineering External Added to denote the issue can be worked on by a contributor
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants