Problem/Motivation
Back in Symfony 4.1 there was a new interface introduced. CachableSupportsMethodInterface: see this blogpost.
Applications can define lots of normalizers/denormalizers and Symfony must call the
supportsNormalization()method for each of them whenever a new object is normalized/denormalized. In theory the result ofsupportsNormalization()can depend on multiple factors. In practice most normalizers only depend on the type and format and that information is easily cacheable.That's the trick used to improve the Serializer performance. We've introduced a new
CacheableSupportsMethodInterfacefor those normalizers/denormalizers that only use the type and the format in theirsupports*()methods:namespace Symfony\Component\Serializer\Normalizer; interface CacheableSupportsMethodInterface { public function hasCacheableSupportsMethod(): bool; }We've already implemented this interface in all the built-in normalizers, so you don't have to change your code. If you have created your own normalizers, check if they can be cached in the same way and implement the interface if needed.
According to our own benchmarks, this change can make simple apps which use few normalizers up to 10% faster. Complex applications with lots of normalizers, such as API Platform apps, can be up to 40% faster.
All normalizer, at lease in JSON:API only check for support in the protected $supportedInterfaceOrClass property. This means we can speed up normalizers theoretically.
The reason we wouldn't want this, if there are normalizers that find out if they support normalization of a value based on something else than the type and format it might break. This is not the case in JSON:API and it is VERY hard to extend those normalizers.
Proposed resolution
Add and implement CacheableSupportsMethodInterface in Drupal\jsonapi\Normalizer\NormalizerBase.
Remaining tasks
Review code change and performance test.
User interface changes
None
API changes
None
Data model changes
None
Release notes snippet
No needed i think?
Performance tests
To test performance i used JsonApiDocumentTopLevelNormalizerTest. I changed the code so that every test runs its body 1000 times. I've run this 13 times with the hasCacheableSupportsMethod returning TRUE and 13 times when it returns FALSE. I've timed how long the test bodies ran for the tests.
Result overview:
| No Change: hasCacheableSupportsMethod(): false | AVG (s) | Performance % |
|---|---|---|
| JsonApiDocumentTopLevelNormalizerTest::testNormalize | 20,49640742 | 100 |
| JsonApiDocumentTopLevelNormalizerTest::testNormalizeUuid | 18,34350703 | 100 |
| JsonApiDocumentTopLevelNormalizerTest::testNormalizeException' | 0,127467706 | 100 |
| JsonApiDocumentTopLevelNormalizerTest::testNormalizeConfig | 0,569442566 | 100 |
| JsonApiDocumentTopLevelNormalizerTest::testDenormalizeUuid | 7,924503657 | 100 |
| JsonApiDocumentTopLevelNormalizerTest::testDenormalizeInvalidTypeAndNoType | 0,825388615 | 100 |
| JsonApiDocumentTopLevelNormalizerTest::testCacheableMetadata | 7,338037656 | 100 |
| 55,62475465 | 100 | |
| Changed: hasCacheableSupportsMethod(): true | AVG (s) | Compared to no change |
| JsonApiDocumentTopLevelNormalizerTest::testNormalize | 19,45793317 | 105,3370224 |
| JsonApiDocumentTopLevelNormalizerTest::testNormalizeUuid | 16,89642024 | 108,5644579 |
| JsonApiDocumentTopLevelNormalizerTest::testNormalizeException' | 0,081131733 | 157,1120206 |
| JsonApiDocumentTopLevelNormalizerTest::testNormalizeConfig | 0,49955832 | 113,9892065 |
| JsonApiDocumentTopLevelNormalizerTest::testDenormalizeUuid | 7,747466546 | 102,2850968 |
| JsonApiDocumentTopLevelNormalizerTest::testDenormalizeInvalidTypeAndNoType | 0,835800519 | 98,75425964 |
| JsonApiDocumentTopLevelNormalizerTest::testCacheableMetadata | 6,94360722 | 105,6804831 |
| 52,46191775 | 106,0288244 |
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | patch_normalizer_for_performance_test.patch | 38.18 KB | bbrala |
| #3 | Nomrlizer performance test.xls | 34.5 KB | bbrala |
Issue fork drupal-3252872
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
bbralaComment #4
bbralaComment #5
bbralaComment #6
bbralaComment #7
bbralaComment #8
bbralaComment #9
bbralaUpdate issue summary and added all files.
Comment #10
bbralaComment #11
gabesulliceNice! I remembered this interface from a while back and found the issue where it was mentioned: #3031214-7: Introduce "deterministic" normalizers
Back then, we concluded that we couldn't use the Symfony interface until Drupal 9... well, D9 is here! Let's do this. I think we can close the original issue as outdated.
I had one nitpick about a missing comment. Other than that, let's mark this RTBC.
Comment #12
bbralaAdded related issue, didn't notice it in the queue. But guess the IS is a bit more up to date here.
Comment #13
bbralaComment #14
bradjones1While the jsonapi PHP API is internal... there are some, ahem, commonly used modules like jsonapi_extras that do things you're "not supposed to do." Is it worth a gander at those we know are out there, and see if there's any implication there?
Comment #15
bbralajsonapi_extras wont return TRUE for that method and therefor isnt affected, tests also run fune with this patch.
I've also checked schemata, which also doesn't seem to be affected, that module only uses the classes from core/serialisation which arn't affected by ths patch.
Should this change also end up in core serialisation it probably needs some more consideration on other extensions.
Comment #16
bbralaComment #17
alexpottI think we need a change record for this one.
It's also tempting to implement a BC layer by checking the class namespace - if it starts with on of Drupal core's namespace - return TRUE. If not trigger a deprecation and return FALSE.
Comment #18
bbralaIve added the change record.
In regards to BC I'm not sure how we would go about that. What would we deprecate on, the fact the method is called but outside core? What would be the advise for the developers; implement the method? What if it should just return TRUE for the other class, should they overwrite eventhough the method is the same as the abstract class to remove the deprecation notice?
Comment #19
bbralaDecided with alexpott that implenting the method in the concrete classes is better for BC. This means classes extending the abstract classes will keep working the same as before.
I've updated the change record and went through al implementations to double checked if they should support caching. Classes that extend other classes i have not changed. For example:
Drupal\jsonapi\Normalizer\EntityReferenceFieldNormalizer extends FieldNormalizer, there i only changedDrupal\jsonapi\Normalizer\FieldNormalizerto allow caching and have not implemented the method in the child classes.Comment #20
alexpottCommitted and pushed 9df13e03446 to 10.0.x and bd858c23131 to 9.4.x. Thanks!
Comment #23
wim leers🥳
Tried to connect all loose ends here, to ensure we maximally follow through now that this step has been taken (thanks, @bbrala!!!):
Comment #24
bbralaThe issue title is wrong oops. Check out the last commit, al normalizers have been updated, also those in hal, serialization and layout_builder modules also.
Comment #25
bbralaUpdated title to reflect the change