Page MenuHomePhabricator

[lib] Avoid creating new array on every invocation of useThreadHasPermission
ClosedPublic

Authored by ashoat on Nov 5 2024, 6:39 AM.
Tags
None
Referenced Files
Unknown Object (File)
Mon, Dec 16, 12:48 PM
Unknown Object (File)
Mon, Dec 16, 6:46 AM
Unknown Object (File)
Sat, Dec 14, 3:16 PM
Unknown Object (File)
Tue, Dec 10, 7:26 PM
Unknown Object (File)
Thu, Dec 5, 9:00 PM
Unknown Object (File)
Thu, Dec 5, 8:56 PM
Unknown Object (File)
Thu, Dec 5, 8:37 PM
Unknown Object (File)
Wed, Dec 4, 11:27 PM
Subscribers
None

Details

Summary

The web app has been really slow to update whenever the ThreadStore changes, so I decided to do some quick profiling on prod using the Chrome Dev Tools.

I noticed that the browser was spending a lot of time on useThreadsWithPermission, so I investigated its callers. That led me to identity this perf issue where we are forcing useThreadsWithPermission to be recalculated on every call to useThreadHasPermission.

Test Plan

Flow

Diff Detail

Repository
rCOMM Comm
Branch
ashoat/perf
Lint
No Lint Coverage
Unit
No Test Coverage

Event Timeline

ashoat published this revision for review.Nov 5 2024, 6:40 AM
ashoat edited the summary of this revision. (Show Details)

Are you sure this improves performance? Did you measure it after making this change and compared it with before the change? Performance improvements are tricky.
As far as I understand useThreadsWithPermission will be called every time useThreadHasPermission is called, even with useMemo. This is a hook, not a component.
Here is a snack with this code but simplified: https://snack.expo.dev/@angelikaserwa/suspicious-violet-tortillas
After you press the button, the app will re-render and useThreadsWithPermission will be called, even with memo.

Are you sure this improves performance?

Unfortunately, after some testing it doesn't seem that this solves the performance issues described in ENG-9862. As such, I'm continuing to investigate... currently working on setting up a custom environment with production data so I can do some better profiling (eg. enable React profiling).

Did you measure it after making this change and compared it with before the change?

I haven't measured directly, but I did do a subjective test and it doesn't seem like the performance is improved much.

That said, I'm fairly sure it's a positive change since we're memoizing something that can cause parts of useThreadsWithPermission to have to be recalculated.

As far as I understand useThreadsWithPermission will be called every time useThreadHasPermission is called, even with useMemo. This is a hook, not a component.

I probably should have explained this differently. What I meant is that we're currently forcing the useMemo in useThreadsWithPermission to be recalculated on every call from useThreadHasPermission. This diff fixes that.

Here is a snack with this code but simplified: https://snack.expo.dev/@angelikaserwa/suspicious-violet-tortillas
After you press the button, the app will re-render and useThreadsWithPermission will be called, even with memo.

I haven't checked the snack as I think we're on the same page about React behavior, but let me know if you think I'm missing something and I'll take a look :)

tomek added 1 blocking reviewer(s): angelika.

I probably should have explained this differently. What I meant is that we're currently forcing the useMemo in useThreadsWithPermission to be recalculated on every call from useThreadHasPermission. This diff fixes that.

Ok, it makes sense, though it's a bit weird to me. This useMemo affects how another hook is calculated. Maybe a comment would be useful?

This revision is now accepted and ready to land.Nov 7 2024, 4:27 AM

Ok, it makes sense, though it's a bit weird to me. This useMemo affects how another hook is calculated.

To be honest, I'm unsure why it's weird to you. On our team, we always memoize non-primitive inputs to hooks. I consider this to be good practice in React, and would advocate for it in any project I work on. I would also request changes on any diff where I saw somebody not memoizing non-primitive inputs to hooks, and hope you would do the same.

React depends a lot on comparing values to determine if things need to be recalculated or rerendered. For non-primitives, such comparisons occur by reference. This is how all dep lists work (such as in React.useMemo), as well as how React.memo works.

Maybe a comment would be useful?

If we left comments in places like this, we would have hundreds of such comments littering the codebase. I frankly don't think it would be useful...

Let's talk about this in our next 1:1. I can explain more about how/why this affects perf, as well as pull up some examples where similar mistakes caused React render cycles for us in the past.

I compiled some notes that we can go through in our next 1:1. Ahead of that, I figured it might help to share a list of React render cycles issues solved by adding React.useMemos like this: