perf: optimize Python and Rust serializer hot paths#340
Merged
Conversation
Contributor
Reviewer's GuideOptimizes the pure-Python JSON-to-XML serializer by fast-pathing native JSON scalar/container types, reducing abstract isinstance dispatch, tightening XML escape/attr handling, and documenting CPython 3.15 performance and flamegraph results alongside matching tests and release metadata updates. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
40c9128 to
d689599
Compare
Contributor
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="json2xml/dicttoxml.py" line_range="517-526" />
<code_context>
+ elif obj_type is int or obj_type is float or obj_type is complex:
</code_context>
<issue_to_address>
**suggestion:** Numeric handling in `_append_convert` is split between exact-type checks and `_is_number`, which complicates the control flow and subclass behavior.
The current ordering treats exact `int`/`float`/`complex` via `type(obj) is ...`, while `Decimal` and other `numbers.Number` subclasses are only caught later via `_is_number`. Likewise, exact `dict`/`list`/`tuple` are handled before `_is_number`, while subclasses rely on later `isinstance` checks. This works but makes subclass behavior dependent on the precise ordering of `type(...) is ...` vs `isinstance(...)` branches. Consider either routing all numerics through `_is_number` or adding a brief comment explaining this intentional split between exact types and subclasses to prevent future regressions.
Suggested implementation:
```python
if obj_type is bool:
output.write(convert_bool(key=item_name, val=obj, attr_type=attr_type, cdata=cdata))
elif obj_type is str:
output.write(convert_kv(key=item_name, val=obj, attr_type=attr_type, attr={}, cdata=cdata))
elif obj is None:
```
This change assumes that a later branch in `_append_convert` (or the surrounding logic) already handles all numeric values via `_is_number(obj)` and converts them appropriately. If such a branch does not yet exist, you should add an `elif _is_number(obj): ...` clause to preserve the previous behavior for `int`/`float`/`complex` and any other `numbers.Number` subclasses.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #340 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 7 7
Lines 714 759 +45
=========================================
+ Hits 714 759 +45
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Exercise every exact-type compatibility fallback, restore string-subclass type metadata, and enforce 100% Python statement coverage.
Document the measured XML escape-scan improvement, gate publication on built-wheel tests, cover Python 3.15 beta, and isolate Rust releases from the Python publisher.
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.
Summary
memchrstrategyAll profiling and benchmarks used uv-managed CPython 3.15.0b3.
Pure Python results
Deterministic 5,000-record nested payload:
isinstancecallsRust results
The symbolized native profile showed the scalar XML escape loop consuming 14.31% of exclusive samples. Hybrid
memchrscanning reduced that to 7.97% while preserving all five substitutions and UTF-8 boundaries. After four sparse matches it switches to monotonic scanners, keeping dense inputs linear.Paired release benchmark: 21 rounds × 50 conversions of the same 5,000-record payload.
The 100,000-record memory benchmark remains at an 80.19 MiB serializer RSS delta for a 78.17 MiB output.
A post-optimization capacity sweep tested 4, 8, 16, 32, 64, and 128 KiB. An ABBA-interleaved confirmation measured 16 KiB at 5.974 ms versus 6.024 ms for 32 KiB, so the existing capacity remains the best measured choice.
The Rust profiles were captured with Samply's macOS native sampler and rendered as SVG flamegraphs with Inferno.
Validation
421 passedon CPython 3.15.0b3 with the Rust extension loaded and exactly 100% statement coveragecargo test: 48 passedcargo clippy --all-targets -- -D warningscargo fmt --checkruff check json2xml teststy check json2xml testslat check