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

Optimize computed props #764

Merged
merged 3 commits into from
Sep 15, 2022
Merged

Conversation

jmyrland
Copy link
Collaborator

An attempt to optimize computed props, by improving comparison of inputs & by checking if the value needs an update.

The value comparison uses a naive comparison of of the JSON stringified values.

This will support more complex inputs & avoid unneccessary updates for complex results (arrays, objects).

related to #732

@jmyrland
Copy link
Collaborator Author

What do you think about this @ctrlplusb ? I haven't testet this against our codebase, but it should work in theory - and should avoid some unnecessary re-renders for computed props.

@ctrlplusb
Copy link
Owner

Hey @jmyrland!

Thanks for jumping in with this PR. Interesting approach. I'd love to test this out in an alpha and see how it performs against some real world projects.

I'll try have a little play. :)

@jmyrland
Copy link
Collaborator Author

Hey @jmyrland!

Thanks for jumping in with this PR. Interesting approach. I'd love to test this out in an alpha and see how it performs against some real world projects.

I'll try have a little play. :)

We did some testing of which "compare"-method was fastest, to minimize performance hits. So far it seems that the lodash.isEqual-implementation is by far the fastest. But the JSON.stringify-version should work fine as a POC.

If you could release an alpha, I can test it in all our apps to see if there is any breaking changes. There is also this sandbox that can verify the change.

I'm also betting that @ROTGP and @no-stack-dub-sack is eager to test out this :)

@ctrlplusb
Copy link
Owner

Sure thing.

So in your investigation we had to perform a deep equality comparison?

I've used this library at a previous client which is pretty blazing;

https://www.npmjs.com/package/fast-deep-equal

An attempt to optimize computed props, by improving
comparison of inputs & by checking if the value needs an update.

The value comparison uses a naive comparison of of the JSON stringified values.

This will support more complex inputs & avoid unneccessary updates for complex results (arrays, objects).

related to ctrlplusb#732
@ctrlplusb
Copy link
Owner

ctrlplusb commented Sep 15, 2022

Aha, I think I just made a good discovery.

When doing comparison the computed property being checked is included in the comparison. This should not be the case. It causes stack overflow issues and could be the root cause for some of the strange edge cases we are seeing around computed properties.

Got some interesting ideas how to approach this, and hopefully have minimal impact on perf too.

@ctrlplusb
Copy link
Owner

@jmyrland

Can you please test out easy-peasy@5.1.0-alpha.1

👍

@jmyrland
Copy link
Collaborator Author

Sure thing.

So in your investigation we had to perform a deep equality comparison?

I've used this library at a previous client which is pretty blazing;

https://www.npmjs.com/package/fast-deep-equal

My theory is that computed arrays & objects are always updated, even if the value is the same - because of the reference comparison (objects & arrays will always fail due to the === comparison).

My attempt to resolve this, was to compare the new & previous computed result before updating the value. In my head, this should avoid unnecessary re-renders for computed arrays & objects.

I'll try out the alpha for the sandbox & report back 👍

@jmyrland
Copy link
Collaborator Author

jmyrland commented Sep 15, 2022

Dang it - it doesn't work for the sandbox.

I was expecting "Bar render" to be 0.

Question: what default state resolvers are used (if not passing in any) to the computed prop?

I'm guessing it is the parent store for the computed prop? If so, is the inputs are different - but the result should be the same, thus there should not be an update for completedTodos.

@jmyrland
Copy link
Collaborator Author

jmyrland commented Sep 15, 2022

It is working - I was just referencing the incorrect alpha version 😁 I updated the sandbox with the correct version.

image

Nice one 🎉🎉

@jmyrland
Copy link
Collaborator Author

We're using a proxied package manager, and I'm not able to find the updated alpha-version. Unable to test it "for real" atm.

@ctrlplusb
Copy link
Owner

Awesome!!

This confirms my thesis. The solution can actually be further optimised. That being said I am gonna merge and release this as a patch so we can track associated bugs around computed properties. If this strategy resolves them I will do the optimisation refactoring.

Will do this in an hour or two. 👍

@jmyrland
Copy link
Collaborator Author

jmyrland commented Sep 15, 2022

Awesome!!

This confirms my thesis. The solution can actually be further optimised.

Sweet!

That being said I am gonna merge and release this as a patch so we can track associated bugs around computed properties. If this strategy resolves them I will do the optimisation refactoring.

Will do this in an hour or two. 👍

I was able to update to the alpha now for our apps - but I sadly get alot of failing tests (~50%) 😢

It seems that computed props are undefined in most cases when a value is expected (it worked previously).

I am unable to test the actual app - as I end up in "login"-redirect loop, as we probably are using some computed props in the auth store.

Maybe this is the culprit?

Testcoverage at the current version: 209 of 375 tests passing.

Edit: Removing the performingEqualityCheck condition returning undefined results in total collapse: 97 / 375 tests passing

Edit 2: Tried updating the performingEqualityCheck case to return prevValue - this results in 366 / 375 tests passing.

Edit 3: There is an issue with computed props referencing other computed props. Adding a state resolver for the dependant computed prop seemed to fix the issue. Now 375 / 375 tests passing after this change. I only have 1 failing test suite now, not accounted for in the total number of tests.

Edit 4: The failing test suite was due to a computed prop not having a state resolver. Fixed by adding this; now 379 / 379 tests passing 🎉

This is just the test result of 1 of 6 apps. I think returning the prevValue in this case in stead of undefined might be the solution here. It is however a breaking change, if users of this lib are creating computed props based on other computed props without state resolvers.

@jmyrland
Copy link
Collaborator Author

Actually, using areInputsEqual just for checking inputs and areEqual for checking if the result changed should be sufficient for avoiding rerenders.

I tested this, and it seems to be working. I.e. if this approach is used, it is not a breaking change - and consumers do not have to update their state resolvers.

@ctrlplusb
Copy link
Owner

ctrlplusb commented Sep 15, 2022

Thanks for your help confirming all this!

Can you try easy-peasy@5.1.0-alpha.2?

@jmyrland

@jmyrland
Copy link
Collaborator Author

Thanks for your help confirming all this!

Can you try easy-peasy@5.1.0-alpha.2?

@jmyrland

A total of 1400~ passing unit tests, without changing a single line 👌🎉
And the sandbox still works as expected.

You are a wizard @ctrlplusb ! 😁

@ctrlplusb
Copy link
Owner

Haha, thanks for the approval @w3bdesign - were you having issues too? Did you try out the alpha release? If so did it resolve your issues?

@ctrlplusb
Copy link
Owner

Woooooooot!

Thanks so much for having such an extensive test suite for us to leverage!

@ctrlplusb ctrlplusb merged commit 7eea5b9 into ctrlplusb:master Sep 15, 2022
@jmyrland
Copy link
Collaborator Author

Happy to help! Also, this may close #732

@no-stack-dub-sack
Copy link
Collaborator

@jmyrland @ctrlplusb Wow! Thanks for the work on this! This is going to greatly simplify a lot of code and reduce the need for state resolvers when trying to avoid un-wanted re-renders. And yes @jmyrland, from what I can tell, the sandbox is working perfectly with completedTodos and safeCompletedTodos both behaving as expected now.

This is great!

jmyrland added a commit that referenced this pull request Sep 16, 2022
* Optimize computed props

An attempt to optimize computed props, by improving
comparison of inputs & by checking if the value needs an update.

The value comparison uses a naive comparison of of the JSON stringified values.

This will support more complex inputs & avoid unneccessary updates for complex results (arrays, objects).

related to #732

* refactor: Modifies computed properties to use fast-deep-equals

* fix: Computed properties optimisations

Co-authored-by: Sean Matheson <sean@ctrlplusb.com>
jmyrland added a commit that referenced this pull request Sep 16, 2022
* Optimize computed props

An attempt to optimize computed props, by improving
comparison of inputs & by checking if the value needs an update.

The value comparison uses a naive comparison of of the JSON stringified values.

This will support more complex inputs & avoid unneccessary updates for complex results (arrays, objects).

related to #732

* refactor: Modifies computed properties to use fast-deep-equals

* fix: Computed properties optimisations

Co-authored-by: Sean Matheson <sean@ctrlplusb.com>
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.

None yet

4 participants