perf(http-client-python): precompute model class state to speed up list operations#10475
perf(http-client-python): precompute model class state to speed up list operations#10475l0lawrence wants to merge 1 commit into
Conversation
…st operations Profiling azure-storage-blob's list_blobs against a 500-blob container showed the TypeSpec runtime deserializer as the dominant hot path: - Model.__init__ rebuilt a defaults dict per instance - _RestField._rest_name was a @Property called once per field per model (73,024 calls for 500 blobs) - Model._calculated used a string set + membership test per subclass - _deserialize_default swallowed all exceptions, masking real bugs This change: * Precomputes cls._defaults once in Model.__new__ * Promotes _rest_name from property to plain attribute set in __new__ * Replaces the _calculated string set with a cls._calculated_done bool (checked via __dict__.get so subclasses don't inherit the flag) * Narrows except Exception to except DeserializationError in _deserialize_default (safe - _deserialize_with_callable already wraps) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
commit: |
|
All changed packages have been documented.
Show changes
|
|
You can try these changes here
|
Cherry-picks the four generator changes from microsoft/typespec#10475 so we can benchmark the list_blobs perf improvements without waiting on a generator release. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Hi @@l0lawrence. Your PR has had no update for 30 days and it is marked as a stale PR. If it is not updated within 30 days, the PR will automatically be closed. If you want to refresh the PR, please remove the |
|
Hi @@l0lawrence. The PR will be closed since the PR has no update for 28 days. If this is still relevant please reopen. |
Summary
Profiling
azure-storage-blob'slist_blobsagainst a 500-blob container on the in-progress TypeSpec migration (Azure/azure-sdk-for-python#45133) identified the generated_utils/model_base.pydeserializer as the dominant hot path – roughly half the wall time was spent there, causing a ~-48% throughput regression vs. the current msrest-generated package. Download/upload were unaffected because they deserialize exactly one model per HTTP call regardless of payload size;list_blobsis the only operation where one response produces N model instances, so per-instance overhead is what matters.This PR addresses the most mechanical of those hot spots in the shared
model_base.py.jinja2template so every TypeSpec-emitted Python SDK benefits.Changes
cls._defaultsonce inModel.__new__–Model.__init__no longer walks_attr_to_rest_fieldon every instance build; it just copies the pre-built mapping._RestField._rest_namefrom@propertyto plain attribute – set once in__new__; removes a descriptor lookup that was hit once per field per model (~73k calls for a 500-blob list)._calculatedset with a_calculated_done: boolflag – checked viacls.__dict__.get("_calculated_done", False)so subclasses re-run the per-class setup correctly without inheriting the flag._deserialize_default'sexcept Exceptiontoexcept DeserializationError– the caller_deserialize_with_callablealready wraps everything intoDeserializationError, so this is semantically equivalent for the success-and-expected-failure paths while surfacing real coding bugs (AttributeError/TypeError) instead of masking them as deserialization failures.No public API change; no generated-code change for consumers beyond the
_utils/model_base.pybody itself.Validation
npm run build– clean.npm run regenerate– unbranded fixtures regenerate successfully. Regenerated_utils/model_base.pycontains the new code paths (_calculated_done,_defaults =,except DeserializationError).tests/unit/test_model_base_serialization.py+test_model_base_xml_serialization.py– 156/157 pass. The one failure (test_null_serialization) is a pre-existing cross-packageisinstancemismatch (azure.core.serialization._Nullvs.corehttp.serialization._Null) in the unchanged_deserialize_with_callabledispatcher and is unrelated to this change – it reproduces onmainwhen bothazure.coreandcorehttpare importable simultaneously in the test env.Follow-ups (not in this PR)
There are larger wins still on the table that I'd like to address in a second PR if this one is well-received:
_init_from_xmlstops recomputing it once per field per instance.typing.get_args/ annotation analysis in_deserializethat currently re-runs for every instance._deserialize_with_callabledispatcher into a precomputed callable per field.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com