Performance & Benchmarks¶
PolyXML is engineered to process gigabytes of XML per second by leveraging Rust's zero-cost abstractions, quick-xml streaming events, and lexical-core numeric conversions.
The repository includes a fully reusable, automated benchmark suite covering both pure Rust Criterion tests and Python comparative benchmarks.
1. Python Deserialization & Serialization Throughput¶
Benchmarks conducted using Python 3.12 (abi3-py312) across 10,000-element streaming payloads (~724 KB XML), micro sensor payloads (~100B), and enterprise orders:
Batch Catalog Workload (10,000 items, ~724 KB XML)¶
| Engine | Paradigm / Category | Implementation | Deserialization Latency | Deserialization Throughput | Serialization Latency | Serialization Throughput | Peak RAM |
|---|---|---|---|---|---|---|---|
| PolyXML | Typed Dataclass | Rust + PyO3 | 13.9 ms | 51.0 MB/s | 7.30 ms | 96.2 MB/s | 2.0 MB |
lxml.objectify |
Dynamic C Object | C / Cython (libxml2) |
9.9 ms | 71.4 MB/s | 3.9 ms | 178.2 MB/s | 0.2 MB |
lxml.etree |
Untyped DOM | C / Cython (libxml2) |
10.0 ms | 70.5 MB/s | — | — | <0.1 MB |
ElementTree |
Untyped DOM | Python Stdlib C/Python | 12.3 ms | 57.5 MB/s | — | — | 7.1 MB |
defusedxml |
Secure DOM | Python Defused | 27.2 ms | 26.0 MB/s | — | — | 7.1 MB |
xmltodict |
Untyped Dict | C (pyexpat) |
56.6 ms | 12.5 MB/s | 79.0 ms | 8.9 MB/s | 4.8 MB |
xsdata |
Typed Dataclass | Pure Python | 222.5 ms | 3.2 MB/s | 282.6 ms | 2.5 MB/s | 3.3 MB |
Key Takeaway: - Vs Typed Dataclasses (
xsdata): PolyXML is 16.0x faster at deserialization and 38.7x faster at serialization, while saving over 1.2 MB of memory. - Vs Dict Parsers (xmltodict): PolyXML is 4.1x faster on deserialization and 10.8x faster on serialization, while instantiating strongly-typed dataclasses instead of unstructured string dictionaries. - Vs C Proxies (lxml.objectify): PolyXML executes within 1.39x of raw C dynamic proxy trees, while returning genuine Python dataclasses with IDE autocomplete and type safety.
Real-Time Micro Telemetry Workload (Sensor ~100B, UCI Telemetry)¶
| Engine | Category | Deserialization Latency | Serialization Latency | Speedup vs Pure Python |
|---|---|---|---|---|
| PolyXML | Typed Dataclass | 2.5 μs | 1.4 μs | 17.1x |
| PolyXML (Pydantic) | Typed Pydantic v2 | 3.1 μs | 1.5 μs | 13.7x |
lxml.etree |
Untyped DOM | 3.1 μs | — | 13.7x |
lxml.objectify |
C Dynamic Object | 3.3 μs | 1.3 μs | 13.1x |
ElementTree |
Untyped DOM | 4.9 μs | — | 8.7x |
defusedxml |
Secure DOM | 8.6 μs | — | 5.0x |
declxml |
Declarative Dict | 10.2 μs | 23.9 μs | 4.2x |
xmltodict |
Untyped Dict | 10.3 μs | 14.9 μs | 4.2x |
pydantic-xml |
Typed Pydantic v2 | 16.4 μs | 15.6 μs | 2.6x |
untangle |
Dynamic Object | 19.1 μs | — | 2.3x |
xsdata |
Typed Dataclass | 43.0 μs | 45.0 μs | 1.0x (Ref) |
Critical telemetry commands and sensor packets deserialize in 2.5 microseconds, beating even C-based DOM parsers (lxml at 3.1 μs, lxml.objectify at 3.3 μs).
2. Pure Rust Core Throughput (crates/polyxml-core)¶
Statistical benchmarks measured with Criterion.rs:
| Workload / Target | Operation | Latency | Throughput | Zero Allocations |
|---|---|---|---|---|
| Sensor Micro (130B) | Deserialization | 1.19 μs | 75.1 MiB/s | Direct scalar parse |
| Sensor Micro (130B) | Serialization | 479 ns | 187.2 MiB/s | Zero intermediate DOM |
| Catalog (1,000 items, ~70 KB) | Deserialization | 1.01 ms | 60.2 MiB/s | Zero intermediate DOM |
| Catalog (1,000 items, ~70 KB) | Serialization | 358 μs | 169.1 MiB/s | Streaming buffer |
| Catalog (10,000 items, ~724 KB) | Deserialization | 10.18 ms | 62.7 MiB/s | Streaming buffer |
| Catalog (10,000 items, ~724 KB) | Serialization | 3.61 ms | 175.2 MiB/s | Streaming buffer |
3. Key-Value Storage & Binary IPC Throughput (polyxml.dumps_binary / loads_binary)¶
When storing XML dataclasses and Pydantic models in embedded transactional key-value databases (libmdbx, LMDB, RocksDB) or communicating over Unix Domain Sockets/multiprocessing queues, Python's traditional cloudpickle and standard pickle encounter severe GIL and GC bottlenecks.
polyxml.dumps_binary() and polyxml.loads_binary() provide high-speed MessagePack encoding with universal leaf type hooks (XmlDate, XmlDateTime, XmlDuration, XmlTime, Decimal, QName, Enum, Path, and Pydantic models):
10,000 Complex XML Entities in Real MDBX Pipeline¶
| Serializer Pipeline | Dumps Ops/s | Dumps Latency | Loads Ops/s | Avg Payload Size | MDBX Write Ops/s |
|---|---|---|---|---|---|
CloudPickle + LZ4 (Legacy) |
20,609 ops/s | 48.5 μs | 49,322 ops/s | 547 B | 18,287 ops/s |
Pickle 5 + LZ4 (Stdlib C) |
71,954 ops/s | 13.9 μs | 50,092 ops/s | 539 B | — |
PolyXML Binary + LZ4 |
163,192 ops/s | 6.1 μs | 64,781 ops/s | 252 B | 58,781 ops/s |
PolyXML Binary (Direct, No LZ4) |
213,003 ops/s | 4.7 μs | 84,673 ops/s | 327 B | 63,236 ops/s |
Key Takeaways:¶
- 7.9x Faster Serialization: Slashes per-object serialization from 48.5 μs down to 6.1 μs.
- 53.9% Storage Space Reduction: Cuts stored byte size from 547 bytes to 252 bytes per entity.
- 3.2x MDBX Transaction Speedup: Real database writes into
libmdbxjump from 18,287 ops/s to 58,781 ops/s. - 100% Fidelity Guarantee: Round-trips preserve exact dataclass types, field metadata, and XML primitive representations with
assert loads(dumps(x)) == x.
4. How to Reproduce Benchmarks¶
The benchmark suite is reusable and version-controlled.