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

[Serializer] Add methods getSupportedTypes to allow better performance #49291

Merged
merged 2 commits into from
Mar 10, 2023

Conversation

tucksaun
Copy link
Contributor

@tucksaun tucksaun commented Feb 7, 2023

Q A
Branch? 6.3
Bug fix? no
New feature? yes
Deprecations? yes (new method in the interfaces, one interface deprecated)
License MIT
Doc PR to be written

The PRs allows normalizers or denormalizers to expose their supported types and the associated cacheability to the Serializer.
With this info, even if the supports* call is not cacheable, the Serializer can skip a ton of method calls to supports* improving performance substaintially in some cases:
Screenshot 2023-02-02 at 15 46 49

I found this design while working on a customer project performance (a big app built around API Platform): we reached the point where the slowest part of main application endpoint was `Symfony\Component\Serializer\Serializer::getNormalizer`.

After some digging, we found out we were experiencing the conjunction of two phenomenons:

  • the application is quite complex and returns deep nested and repeating structures, exposing the underlying bottleneck;
  • and a lot of custom non-cacheable normalizers.
    Because most of the normalizers are not cacheable, the Serializer has to call every normalizer over and over again leading getNormalizer to account for 20% of the total wall time:
    Screenshot 2023-02-07 at 16 56 02

We first tried to improve cacheability based on context without much success, then an approach similar to #45779 with some success but still feeling this could be faster.
We then thought that even if the supportsNormalization could not be cached (because of the context), maybe we could avoid the calls at the origin by letting the Normalizers expose the types they support and came to this PR with pretty good results.

The perfornance improvement was only measured by adapting Symfony's normalizers as well as the project ones, proper third party normalizers updates should improve performance even more.

This should effectively replaces the CacheableSupportsMethodInterface as the cacheability can now be returned by getSupportedTypes.

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

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

Nice improvement!

I would go as far as suggesting to add getSupportedTypes to both NormalizerInterface and DenormalizerInterface (with a BC layer of course)? That'd make a lot of sense to me.

@stof
Copy link
Member

stof commented Feb 8, 2023

the new interface needs to be implemented in the TraceableNormalizer, so that the benefits of this caching are not lost (and hidden from devs) in dev environments when the profiler is enabled.

@dunglas
Copy link
Member

dunglas commented Feb 8, 2023

This looks very promising! +1 on my side for the general approach as well as for making supportsNormalization() optional.

@tucksaun tucksaun force-pushed the serializer/speed-improvement branch 3 times, most recently from e11f040 to 25b94bc Compare February 10, 2023 20:57
@tucksaun tucksaun force-pushed the serializer/speed-improvement branch 2 times, most recently from d103aa6 to 84b2667 Compare February 18, 2023 00:29
@tucksaun tucksaun force-pushed the serializer/speed-improvement branch 6 times, most recently from db028b5 to 2e9daf0 Compare February 18, 2023 00:47
@tucksaun
Copy link
Contributor Author

@nicolas-grekas PR has been updated to move getSupportedTypes to NormalizerInterface and DenormalizerInterface

@fabpot
Copy link
Member

fabpot commented Mar 10, 2023

Thank you @tucksaun.

Copy link
Contributor Author

@tucksaun tucksaun left a comment

Choose a reason for hiding this comment

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

Thanks @nicolas-grekas for taking over the last edits regarding your comment 👍
Also I like the idea or the wildcard: stating that a normalizer supports any type and is cacheable was indeed something my PR was lacking!

* For each supported formats (if applicable), the supported types should be
* returned as keys, and each type should be mapped to a boolean indicating
* if the result of supportsDenormalization() can be cached or not
* (a result cannot be cached when it depends on the context or on the data.)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"(a result cannot be cached when it depends on the context or on the data)." (dot after the parenthesis) ?

* For each supported formats (if applicable), the supported types should be
* returned as keys, and each type should be mapped to a boolean indicating
* if the result of supportsNormalization() can be cached or not
* (a result cannot be cached when it depends on the context or on the data.)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"(a result cannot be cached when it depends on the context or on the data)." (dot after the parenthesis) ?

nicolas-grekas added a commit that referenced this pull request Mar 28, 2023
…portedTypes()" (nicolas-grekas)

This PR was merged into the 6.3 branch.

Discussion
----------

[Serializer] Allow filtering "object" when using "getSupportedTypes()"

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | -

Follows #49291

This allows telling when only objects are accepted, to skip calling `supportsNormalization()` when native types are considered.

/cc `@tucksaun`

Commits
-------

6984dce [Serializer] Allow filtering "object" when using "getSupportedTypes()"
tucksaun added a commit to tucksaun/symfony that referenced this pull request Apr 5, 2023
| Q             | A
| ------------- | ---
| Branch?       | 6.3 for features
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | symfony#49291 (review)
| License       | MIT
| Doc PR        | n/a

As mentioned in symfony#49291 (review).
tucksaun added a commit to tucksaun/symfony that referenced this pull request Apr 5, 2023
| Q             | A
| ------------- | ---
| Branch?       | 6.3 for features
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | symfony#49291 (review)
| License       | MIT
| Doc PR        | n/a

As mentioned in symfony#49291 (review).
chalasr added a commit that referenced this pull request Apr 5, 2023
This PR was merged into the 6.3 branch.

Discussion
----------

[Serializer] Add missing upgrade note

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | none
| License       | MIT
| Doc PR        | none

Forgot during #49291

/cc `@nicolas`-grekas

Commits
-------

a28ff01 [Serializer] Add missing upgrade note
tucksaun added a commit to tucksaun/symfony that referenced this pull request Apr 5, 2023
| Q             | A
| ------------- | ---
| Branch?       | 6.3 for features
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | symfony#49291 (review)
| License       | MIT
| Doc PR        | n/a

As mentioned in symfony#49291 (review).
fabpot added a commit that referenced this pull request Apr 8, 2023
…(tucksaun)

This PR was merged into the 6.3 branch.

Discussion
----------

[Serializer] Mark `ObjectNormalizer` as final for 7.0

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | #49291 (review)
| License       | MIT
| Doc PR        | none

Extracted from #49950 and related to #49291 (review)

Commits
-------

b2828e9 [Serializer] Mark ObjectNormalizer as final for 7.0
tucksaun added a commit to tucksaun/symfony that referenced this pull request Apr 8, 2023
| Q             | A
| ------------- | ---
| Branch?       | 6.3 for features
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | symfony#49291 (review)
| License       | MIT
| Doc PR        | n/a

As mentioned in symfony#49291 (review).
fabpot added a commit that referenced this pull request Apr 9, 2023
…(tucksaun)

This PR was merged into the 6.3 branch.

Discussion
----------

[Serializer] Marking some Normalizer classes as final

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | #49291 (review)
| License       | MIT
| Doc PR        | n/a

As mentioned in #49291 (review).

Making those classes final will allow to get rid of all the `__CLASS__ === static::class` for cacheability checks in 7.0

Commits
-------

d976fc5 [Serializer] Marking some Normalizer classes as final
symfony-splitter pushed a commit to symfony/serializer that referenced this pull request Apr 9, 2023
| Q             | A
| ------------- | ---
| Branch?       | 6.3 for features
| Bug fix?      | no
| New feature?  | no
| Deprecations? | yes
| Tickets       | symfony/symfony#49291 (review)
| License       | MIT
| Doc PR        | n/a

As mentioned in symfony/symfony#49291 (review).
@fabpot fabpot mentioned this pull request May 1, 2023
nicolas-grekas added a commit that referenced this pull request Jun 1, 2023
…rmalizableInterface (spideyfusion)

This PR was merged into the 6.3 branch.

Discussion
----------

[Serializer] Fix ignoring objects that only implement DenormalizableInterface

| Q             | A
| ------------- | ---
| Branch?       | 6.3 <!-- see below -->
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | N/A <!-- prefix each issue number with "Fix #", no need to create an issue if none exists, explain below instead -->
| License       | MIT
| Doc PR        | N/A <!-- required for new features -->

When the #49291 optimization got introduced, the `DenormalizableInterface` type was left out of the `CustomNormalizer` normalizer's `getSupportedTypes` implementation.

This prevents us for example, implementing custom deserialization logic for API objects where we can't describe all deserialization rules via attributes.

<!--
Replace this notice by a short README for your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - Bug fixes must be submitted against the lowest maintained branch where they apply
   (lowest branches are regularly merged to upper ones so they get the fixes too).
 - Features and deprecations must be submitted against the latest branch.
 - For new features, provide some code snippets to help understand usage.
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

Commits
-------

a0a3ce1 [Serializer] Fix ignoring objects that only implement DenormalizableInterface
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants