Skip to content

Latest commit

 

History

History
200 lines (149 loc) · 12.7 KB

index.md

File metadata and controls

200 lines (149 loc) · 12.7 KB
title Benchmarks
description Benchmark coverage, published results, methods, and reproduction commands for every PolyXML target language.

Benchmarks

PolyXML has benchmark suites for all seven target languages, plus WebAssembly and CLI startup. The suites answer different questions, so compare numbers within the same workload and runtime. A quick smoke run confirms that a suite works; it is not a performance result.

For a direct native/competitor/PolyXML view, start with the October 2026 serializer comparison tables. They separate XML from JSON, typed results from dynamic graphs, and generated models using native serializers from independent binding engines.

Choose a benchmark

Target What it measures Suite and instructions Published results
Rust Core XML read/write; tag dispatch, generated decoder check, and compile cost Criterion suites, generated decoder runner, compile-cost runner Core results, dispatch study
Python Typed binding vs Python XML libraries; JSON vs xsdata; binary vs pickle; AOT Python suite, JSON, Binary, AOT XML results, JSON, binary & AOT study, AOT study
Java Generated POJOs, JAXB, Jackson, and Panama JMH suite September 2026 measurements
Go Generated structs vs equivalent handwritten structs, both using encoding/xml Go suite September 2026 measurements
C++ Native C-ABI binding on shared batches plus narrow adapter sanity check C++ suite September 2026 measurements
C# Generated vs handwritten classes, both using XmlSerializer C# suite September 2026 measurements
TypeScript & Wasm Wasm vs JavaScript parsers in Node, Bun, and Chromium TypeScript/Wasm suite Wasm vs JavaScript study, shared sensor run

The shared sensor workload feeds the same XML bytes to readers in all seven targets. Its September 2026 results identify each binding layer and explain why a shared input alone does not justify a cross-language speed ranking.

The CLI startup suite measures command startup and argument validation separately from language bindings. The repository suite catalog lists every entry point.

!!! note "Reading the C++ numbers" PolyXML provides a native C++20 binding (polyxml.hpp) over the Rust C ABI supporting dynamic nested and list schemas and mapping to generated C++20 models. A separate legacy narrow adapter is retained purely as a fixture sanity check. Keep their timings separate; do not cite the string-search adapter as XML parser throughput.

Published studies

The older Python and Rust result tables below preserve their original workloads. Use each linked method and environment when interpreting those figures.

🚀 Looking for architectural comparisons with legacy compilers? Check out Why PolyXML? (The Architecture of Modern XML) for in-depth comparisons against JAXB, CodeSynthesis, xsdata, xgen, and xsd.exe.


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. The committed results and runner support the tables below.

⚡ AOT Native Compilation: In addition to standard dataclasses, PolyXML can compile schemas directly into native C-extensions via --backend aot. See the full Python AOT Native Extension vs Dataclass Benchmark (3.4x faster throughput, 60.3% less memory).

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 24.0 ms 29.8 MB/s 12.2 ms 58.1 MB/s 2.0 MB
lxml.objectify Dynamic C Object C / Cython (libxml2) 11.8 ms 61.0 MB/s 4.1 ms 173.5 MB/s 0.2 MB
lxml.etree Untyped DOM C / Cython (libxml2) 11.8 ms 61.1 MB/s — — <0.1 MB
ElementTree Untyped DOM Python Stdlib C/Python 13.6 ms 53.0 MB/s — — 7.1 MB
defusedxml Secure DOM Python Defused 29.5 ms 24.2 MB/s — — 7.1 MB
xmltodict Untyped Dict C (pyexpat) 57.5 ms 12.5 MB/s 80.0 ms 8.9 MB/s 4.8 MB
xsdata Typed Dataclass Pure Python 241.9 ms 3.0 MB/s 298.8 ms 2.5 MB/s 3.3 MB

Key Takeaway:

  • Vs Typed Dataclasses (xsdata): PolyXML is 10.0x faster at deserialization and 23.5x faster at serialization, while saving over 1.3 MB of memory.
  • Vs Dict Parsers (xmltodict): PolyXML is 4.2x faster on deserialization and 6.5x faster on serialization, while instantiating strongly-typed dataclasses instead of unstructured string dictionaries.
  • Vs C Proxies (lxml.objectify): PolyXML executes within ~2x of raw C dynamic proxy trees, while returning genuine, fully typed 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 3.2 μs 1.8 μs 13.9x
PolyXML (Pydantic) Typed Pydantic v2 3.8 μs 1.9 μs 11.8x
lxml.etree Untyped DOM 3.3 μs — 13.4x
lxml.objectify C Dynamic Object 3.3 μs 1.4 μs 13.1x
ElementTree Untyped DOM 5.3 μs — 8.4x
defusedxml Secure DOM 9.1 μs — 4.9x
declxml Declarative Dict 10.2 μs 23.9 μs 4.2x
xmltodict Untyped Dict 10.6 μs 15.4 μs 4.2x
pydantic-xml Typed Pydantic v2 17.1 μs 15.5 μs 2.6x
untangle Dynamic Object 19.5 μs — 2.3x
xsdata Typed Dataclass 44.5 μs 45.5 μs 1.0x (Ref)

Critical telemetry commands and sensor packets deserialize in 3.2 microseconds, neck-and-neck with C-based DOM parsers (lxml at 3.3 μs).


2. Pure Rust Core Throughput (crates/polyxml-core)

Statistical benchmarks measured with Criterion.rs; benchmark source and reproduction command. These historical figures have no committed raw Criterion report, so rerun before using them for a new performance claim:

Workload / Target Operation Latency Throughput Zero Allocations
Sensor Micro (130B) Deserialization 1.19 μs 75.0 MiB/s Direct scalar parse
Sensor Micro (130B) Serialization 456 ns 196.5 MiB/s Zero intermediate DOM
Catalog (1,000 items, ~70 KB) Deserialization 1.00 ms 60.6 MiB/s Zero intermediate DOM
Catalog (1,000 items, ~70 KB) Serialization 305 μs 198.3 MiB/s Streaming buffer
Catalog (10,000 items, ~724 KB) Deserialization 10.54 ms 60.5 MiB/s Streaming buffer
Catalog (10,000 items, ~724 KB) Serialization 3.20 ms 197.8 MiB/s Streaming buffer

3. How to Reproduce Benchmarks

The benchmark suite is reusable and version-controlled.

🛡️ Memory safety: the suite entry points below run under scripts/memcap.sh, which caps each run at 60% of available RAM in an isolated cgroup (kernel OOM-kills only the runaway build; the host stays responsive — ulimit fallback where systemd is unavailable). Wrap any ad-hoc cargo bench --release the same way; tune with POLYXML_MEMCAP_PCT, opt out with POLYXML_MEMCAP_DISABLE=1.

Run the unified Rust and Python suites

./benchmarks/run_all.sh

Run Pure Rust Criterion Benchmarks

cargo bench --bench core_benchmarks
cargo bench --bench tag_dispatch   # dispatch strategy suite: docs/benchmarks/rust-phf-dispatch.md

Run Python Comparative Benchmarks CLI

python -m benchmarks.python --workload all --catalog-sizes 1000 10000 --iterations 25 --output-md benchmarks/python/results.md --output-json benchmarks/python/results.json

Run the Java Four-Runtime JMH Suite

mvn -f benchmarks/java/pom.xml clean package

See the Java benchmark README for the Panama profile, workload definitions, and interpretation caveats.

Run the Go, C++, and C# suites

Build the local CLI once, then run each suite from the repository root:

cargo build -p polyxml-cli
./benchmarks/go/run.sh
./benchmarks/cpp/run.sh
./benchmarks/csharp/run.sh

Each runner generates its model into an ignored target/ directory, checks a round trip, and measures small and 1,000-sensor batches. See the linked suite READMEs above for toolchain requirements and interpretation limits. Use their short-iteration settings only to check that they execute.

Run the TypeScript/Wasm suite

The TypeScript/Wasm README has separate commands for Node, Bun, and headless Chromium, plus instructions for the larger fixture. These lanes are intentionally separate from the unified Rust/Python runner.

For publishable measurements, run on an otherwise idle host, repeat the full suite, and keep raw output with the Git revision, CPU/OS, and toolchain versions. Compare read with read and write with write on the same payload; generated models, DOM parsers, and transcoding pipelines can return different values.