Performance & Benchmarks¶
pyxsdata-core is engineered from the ground up to solve Python's CPU bound serialization bottlenecks when handling massive XML feeds, high-frequency webhooks, and enterprise SOAP/XML microservices.
Executive Summary¶
| Target Model Format | Pure Python (xml.etree) |
pyxsdata-core (Rust) |
Throughput Speedup |
|---|---|---|---|
Pydantic v2 BaseModel |
24.6 ms / 1,000 items (40,580 objs/s) | 3.2 ms / 1,000 items (311,245 objs/s) | ~7.67x faster |
Python Standard @dataclass |
33.2 ms / 1,000 items (30,120 objs/s) | 3.4 ms / 1,000 items (294,117 objs/s) | ~9.50x faster |
Legacy xsdata |
51.3 ms / 1,000 items (19,490 objs/s) | 3.4 ms / 1,000 items (294,117 objs/s) | ~15.0x faster |
(Benchmark run on AMD Ryzen / Linux x86_64, CPython 3.12.14, lowest of 5 runs over 10,000 complex nested XML items)
Benchmark Comparison¶
gantt
title Latency for Parsing 1,000 XML Items (Lower is Better)
dateFormat X
axisFormat %s ms
section Legacy xsdata
xsdata standard (51.3 ms) : 0, 51
section Standard Python
Pure Python dataclass (33.2 ms) : 0, 33
Pure Python Pydantic v2 (24.6 ms) : 0, 25
section pyxsdata-core
Rust Core dataclass (3.4 ms) : 0, 3
Rust Core Pydantic v2 (3.2 ms) : 0, 3
Real-World Enterprise Benchmark: UCI Entity Message¶
Parsing production-grade, deeply nested Universal Command and Control Interface (UCI v2.5)
Entity telemetry messages (with security markings, timestamps, headers, metadata, and enums):
| Deserializer Engine | Latency / Message | Throughput | Speedup vs Pure Python |
|---|---|---|---|
pyxsdata-core |
16.5 µs | ~60,360 msgs/s | ~11.47x (1,047% faster) |
XmlParser (Pure Python) |
190.0 µs | ~5,262 msgs/s | 1.0x (Baseline) |
Why Is pyxsdata-core So Fast?¶
Traditional Python XML parsers (including xml.etree.ElementTree, lxml, and SAX) use a 4-step pipeline:
flowchart TD
A[XML Bytes] -->|Byte stream| B[C / C++ Parser]
B -->|Allocate Element Object| C[Python Intermediate DOM Tree]
C -->|Iterator / SAX Event Pump| D[Python Event Dispatcher]
D -->|Dict lookup / converters| E[Python Model Converter]
E -->|Constructor Call| F[Target Python Model]
style C fill:#fbb,stroke:#d00,stroke-width:2px
style D fill:#fbb,stroke:#d00,stroke-width:2px
The Cost of the Intermediate Pipeline¶
- Intermediate Allocations: Creating an
Elementor tuple event for every tag, attribute, and text chunk allocates millions of temporary Python objects that immediately enter garbage collection. - Interpreter Overhead: Running event loop dispatch and type conversion in Python byte-code executes hundreds of opcodes per tag.
- Double Conversion: Text is decoded into Python
str, inspected, converted to primitives, then copied into dictionaries.
The pyxsdata-core Architecture¶
pyxsdata-core eliminates steps 2, 3, and 4:
flowchart TD
A[XML Bytes] -->|Zero-Copy Token Stream| B[quick-xml Pull Parser in Rust]
B -->|Match Token to Precompiled ModelSchema| C[Rust Type Resolver & Buffer]
C -->|Direct PyO3 C-API Tuple / Keyword Call| D[Target Python Model]
style B fill:#bfb,stroke:#090,stroke-width:2px
style C fill:#bfb,stroke:#090,stroke-width:2px
style D fill:#bfb,stroke:#090,stroke-width:2px
- Zero Intermediate Objects: No Python elements, nodes, or event dictionaries are created for intermediate tags.
- Zero-Copy Tokenization:
quick-xmlparses tag slices directly from the input byte buffer without copying. - Rust Type Conversion: Integers, floats, and booleans are parsed directly into CPython primitive representations (
PyLong,PyFloat,PyBool) in Rust before calling the constructor. - Direct C-API Instantiation: Once all fields for a record are assembled, the Python class constructor (
tp_call) is invoked once with C-level keyword arguments.
Reproducing the Benchmarks¶
You can benchmark your local system directly using the test fixtures:
import time
from pydantic import BaseModel
import pyxsdata_core
class Item(BaseModel):
id: int
name: str
price: float
xml_doc = b"<Item><id>101</id><name>Turbine</name><price>499.99</price></Item>"
# Warmup
for _ in range(100):
_ = pyxsdata_core.deserialize(xml_doc, Item)
# Measure 100,000 iterations
count = 100_000
start = time.perf_counter()
for _ in range(count):
_ = pyxsdata_core.deserialize(xml_doc, Item)
duration = time.perf_counter() - start
rate = count / duration
print(f"Parsed {count:,} items in {duration:.3f}s ({rate:,.0f} items/sec)")