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 regressions on v3 #716

Closed
radekmie opened this issue May 1, 2020 · 2 comments
Closed

Performance regressions on v3 #716

radekmie opened this issue May 1, 2020 · 2 comments
Assignees
Labels
Type: Feature New features and feature requests
Milestone

Comments

@radekmie
Copy link
Contributor

radekmie commented May 1, 2020

Step by step, commit by commit - we're getting closer to a state, where v3 could be released (finally!). You don't have to run benchmarks to see, that the new version is far slower! Let's dissect the problems and think about possible solutions.

Consequences of new context API

Using useField, or useForm directly, relies on useContext underneath. While the new context API is far better and more composable, it differs in the way of context propagation. The old context was more or less another set of props. This caused a lot of issues with other packages using PureComponent/memo (like #528). New one updates all consumers directly, skipping all nodes in between. This means, that there's no way to actually stop an update on the consumer side, and existing shouldComponentUpdate won't help us now.

As there's no React way to deal with that yet (there are RFCs though, like reactjs/rfcs#119), we have to do something. There are userspace implementations, like dai-shi/use-context-selector. I think we could use that, but the actual gains need to be confirmed in benchmarks. This would also mean, that the actual context usage may change with time, and we should no longer treat the context as a part of public API (of course, useForm and useField are still available).

Consequences of new lifecycle APIs

Using componentWillReceiveProps one can access both previous and next props as well as the current state. New getDerivedStateFromProps differs a little: there's no access to previous props. This means, that the BaseForm cannot recreate the bridge only when needed! I've signalized it already and we should consider actually implementing that.

EDIT: Other solution can be actually removing createSchemaBridge completely. This would eliminate the awkward Bridge.check as well as the bridge duplication problem because one would have to create the bridge manually beforehand. It leads to a slightly more complicated usage with SimpleSchema, but it changes nothing with JSON schema GraphQL and schemas.

Other problems

I'm sure there are more things we could do. We have to take the opportunity of making breaking changes (as small as possible!) and look for them now. One thing we could take a look at is the modelSync of AutoForm - it may be not needed with the Suspend API.

@radekmie radekmie added the Type: Feature New features and feature requests label May 1, 2020
@radekmie radekmie added this to the v3 milestone May 1, 2020
@radekmie radekmie self-assigned this May 1, 2020
This was referenced May 1, 2020
@radekmie
Copy link
Contributor Author

radekmie commented May 3, 2020

Changes made in e27ea07, ddba4da, and 69d2b78 improved performance of big schemas a lot.

Nested v3 0ad207a v3 69d2b78 v2 759a0a7
min 78ms 42ms 55ms
max 116ms 70ms 62ms

All times were recorded while changing the last field of the tested schema to trigger as many updates as possible. While editing the first one, v3 does exactly the same work (no context selectors yet) but v2 is much faster because the whole deep subtree stops at the very beginning due to shouldComponentUpdate (~14ms).

The performance gains on flat schemas are also visible, but not so big.

Flat v3 0ad207a v3 69d2b78 v2 759a0a7
min 62ms 51ms 22ms
max 91ms 73ms 57ms
Nested schema.
new SimpleSchema2Bridge(
  new SimpleSchema({
    a: String,
    b: String,
    c: String,
    d: String,
    e: new SimpleSchema({
      a: String,
      b: String,
      c: String,
      d: String,
      e: new SimpleSchema({
        a: String,
        b: String,
        c: String,
        d: String,
        e: new SimpleSchema({
          a: String,
          b: String,
          c: String,
          d: String,
          e: new SimpleSchema({
            a: String,
            b: String,
            c: String,
            d: String,
            e: new SimpleSchema({
              a: String,
              b: String,
              c: String,
              d: String,
              e: new SimpleSchema({
                a: String,
                b: String,
                c: String,
                d: String,
                e: new SimpleSchema({
                  a: String,
                  b: String,
                  c: String,
                  d: String,
                  e: new SimpleSchema({
                    a: String,
                    b: String,
                    c: String,
                    d: String,
                    e: new SimpleSchema({
                      a: String,
                      b: String,
                      c: String,
                      d: String,
                      e: new SimpleSchema({
                        a: String,
                        b: String,
                        c: String,
                        d: String,
                        e: new SimpleSchema({
                          a: String,
                          b: String,
                          c: String,
                          d: String,
                          e: new SimpleSchema({
                            a: String,
                            b: String,
                            c: String,
                            d: String,
                            e: new SimpleSchema({
                              a: String,
                              b: String,
                              c: String,
                              d: String,
                              e: String
                            })
                          })
                        })
                      })
                    })
                  })
                })
              })
            })
          })
        })
      })
    })
  })
)
Flat schema
new SimpleSchema2Bridge(
  new SimpleSchema({
    a00: String,
    a01: String,
    a02: String,
    a03: String,
    a04: String,
    a05: String,
    a06: String,
    a07: String,
    a08: String,
    a09: String,
    a10: String,
    a11: String,
    a12: String,
    a13: String,
    a14: String,
    a15: String,
    a16: String,
    a17: String,
    a18: String,
    a19: String,
    a20: String,
    a21: String,
    a22: String,
    a23: String,
    a24: String,
    a25: String,
    a26: String,
    a27: String,
    a28: String,
    a29: String,
    a30: String,
    a31: String,
    a32: String,
    a33: String,
    a34: String,
    a35: String,
    a36: String,
    a37: String,
    a38: String,
    a39: String,
    a40: String,
    a41: String,
    a42: String,
    a43: String,
    a44: String,
    a45: String,
    a46: String,
    a47: String,
    a48: String,
    a49: String,
    a50: String,
    a51: String,
    a52: String,
    a53: String,
    a54: String,
    a55: String,
    a56: String,
    a57: String,
    a58: String,
    a59: String,
    a60: String,
    a61: String,
    a62: String,
    a63: String,
    a64: String,
    a65: String,
    a66: String,
    a67: String,
    a68: String,
    a69: String,
    a70: String,
    a71: String,
    a72: String,
    a73: String,
    a74: String,
    a75: String,
    a76: String,
    a77: String,
    a78: String,
    a79: String,
    a80: String,
    a81: String,
    a82: String,
    a83: String,
    a84: String,
    a85: String,
    a86: String,
    a87: String,
    a88: String,
    a89: String,
    a90: String,
    a91: String,
    a92: String,
    a93: String,
    a94: String,
    a95: String,
    a96: String,
    a97: String,
    a98: String,
    a99: String,
  })
)

@radekmie
Copy link
Contributor Author

radekmie commented Jun 6, 2020

With #739 and #717 (implemented in #741) the performance is already good enough. I've performed some experiments regarding the context and it's not helping us much as long as the schema is not very deep and narrow (which is usually not the case). There are two more things we should consider doing:

  • Using memo when possible (especially for simple fields like TextField). It gives a big performance boost as the props rarely change. There's a problem though - memoed components don't have explicit name nor displayName. There's an issue to have an API for that though: API for display name on forwardRef, memo and potential future exotic components facebook/react#14319. Some quick experiments show massive improvements, especially with Added kind option to connectField. #741 in place.
  • Use a custom Provider for the context. I wouldn't like to rework the way how we use context now (maybe in v4...?), therefore each render produces a new context object, ultimately re-rendering all fields. What we could do is to make a custom wrapper for the provider that preserves the reference as long as the next one is equivalent (i.e. would render the same form). I've prepared some basic version and it can help a lot, especially when fields are deeply nested.

Both of these could be added later as they won't break the compatibility. Therefore, I consider this task done after #739 and #741 get merged.

@Monteth Monteth closed this as completed Jun 10, 2020
@Monteth Monteth reopened this Jun 10, 2020
@radekmie radekmie moved this to Closed in Open Source Nov 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Feature New features and feature requests
Projects
Archived in project
Development

No branches or pull requests

2 participants