THRIFT-5943: optimize python C extension readStruct for nested structs#3377
Open
markjm wants to merge 1 commit intoapache:masterfrom
Open
THRIFT-5943: optimize python C extension readStruct for nested structs#3377markjm wants to merge 1 commit intoapache:masterfrom
markjm wants to merge 1 commit intoapache:masterfrom
Conversation
171c028 to
4d0914c
Compare
In this area of code, there already exists an optimization where we skip calling `klass(**kwargs)` on the top-level struct and instead init an empty object then PyObject_SetAttr the properties onto it. This was done because Python's keyword argument matching is O(n) string comparisons per argument, making this expensive for structs with many fields. However, nested structs which are decoded within the C extension did not get this same optimization, causing them to go through the slow class initialization path. In this change, for mutable nested structs, we create the instance up front with a no-arg constructor (klass()) and set decoded fields directly via PyObject_SetAttr (the same way we fast-path the top-level struct). Immutable structs (TFrozenBase subclasses) continue to use the kwargs path since their generated __setattr__ blocks attribute mutation. Immutability is detected via PyObject_IsSubclass against TFrozenBase, with the class reference cached in a function-local static. Benchmarks show up to 3.6x speedup for deeply nested struct hierarchies, with no impact on flat structs. I had Claude generate me some benchmarks to showcase performance in different nested scenarios. Note the test change. I asked about potentially changing this to look more like what the codegen produces here https://github.com/apache/thrift/pull/3349/changes#r3026288637. Currently, this fails because its not marked as frozen but has a erroring setattr
4d0914c to
e45a8f7
Compare
Contributor
Author
|
Regarding:
the test suite alerted me to another mechanism which something could look frozen which i needed to handle by checking 2 options. with that I dont need to modify the tests |
Contributor
Author
|
thanks for kicking the builds @fishy - all green 🕺 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In this area of code, there already exists an optimization where we skip calling
klass(**kwargs)on the top-level struct and instead init an empty object thenPyObject_SetAttrthe properties onto it. This was done because Python's keyword argument matching is O(n) string comparisons per argument, making this expensive for structs with many fields.However, nested structs which are decoded within the C extension did not get this same optimization, causing them to go through the slow class initialization path.
In this change, for mutable nested structs, we create the instance up front with a no-arg constructor (
klass()) and set decoded fields directly viaPyObject_SetAttr(the same way we fast-path the top-level struct).Immutable structs (
TFrozenBasesubclasses) continue to use the kwargs path since their generated__setattr__blocks attribute mutation.Immutability is detected via (1)
PyObject_IsSubclassagainstTFrozenBasewith the class reference cached in a function-local static or (2) and overridden__setattr__.Benchmarks show up to 3.6x speedup for deeply nested struct hierarchies, with no impact on flat structs. I had Claude generate me some benchmarks to showcase performance in different nested scenarios:
https://gist.github.com/markjm/e8610298d30d32869f46cf521272c85c
Checklist
[skip ci]anywhere in the commit message to free up build resources.