Skip to content

Performance

All numbers below come from cargo bench -p sup-xml-bench on the checked-in fixture set. Each table is reproducible with the command shown above it.

Parse — DOM (matched contract vs libxml2)

Both columns use SupXML’s bumpalo-backed arena DOM (crates/tree/src/arena.rs) and libxml2’s xmlParseMemory. Both validate UTF-8, validate XML 1.0 § 2.2 characters, expand general entities, enforce end-tag matching, and build an owned tree — so the comparison is apples-to-apples. Throughput, MB/s; ratio = sup-xml / libxml2, so >1 means SupXML faster:

fixturesizesup-xmllibxml2ratio
321gone23 KB438 MB/s211 MB/s2.08×
183189315 KB570 MB/s172 MB/s3.32×
chinese17.9 MB346 MB/s162 MB/s2.13×
customer1503 KB370 MB/s204 MB/s1.82×
ebay34 KB910 MB/s491 MB/s1.86×
gazali_maqasid_ar599 KB539 MB/s144 MB/s3.75×
nasa24 MB362 MB/s161 MB/s2.25×
pubmed600 KB349 MB/s217 MB/s1.61×
sitemap1.0 MB396 MB/s196 MB/s2.02×
swiss_prot95 MB177 MB/s94 MB/s1.88×
wikipedia_ww2252 KB1170 MB/s395 MB/s2.96×

SupXML is faster on every fixture; the median ratio across the full 21-fixture set is ~2.1×, with the spread driven by attribute density (entity-heavy gazali_maqasid_ar is the high end) and validation intensity (large mostly-ASCII swiss_prot is the low end). Reproduce with:

Terminal window
cargo bench -p sup-xml-bench --bench head_to_head

Parse — SAX / streaming

The XmlBytesReader zero-copy streaming reader, byte events, matched against quick-xml’s Reader at quick-xml’s own lighter contract (no end-tag matching, no UTF-8 validation, raw byte slices):

fixturesup-xml (bytes)quick-xml (raw)ratio
customer11240 MB/s1188 MB/s1.04×
ebay3094 MB/s3160 MB/s0.98×
nasa822 MB/s1107 MB/s0.74×
swiss_prot416 MB/s705 MB/s0.59×
wikipedia_ww23650 MB/s32320 MB/s0.11×

Median across all 21 fixtures is ~1.04× in SupXML’s favour at the matched contract. The wikipedia_ww2 entry is an outlier: the fixture fits in L1, both parsers ship a fast path that defeats memory bandwidth estimation, and quick-xml’s number there is unrepresentative of any realistic workload — it just measures how fast you can spin a loop on a hot byte buffer.

For documents that don’t fit in memory, the XmlBytesReader reads bytes with a rolling memory window — see the parsing guide for the API.

XSD validation — head-to-head conformance + wall-clock

The W3C XSD 1.0 test suite (XSTS 2006-11-06) covers 14,328 schemaTests and 25,092 instanceTests across four contributors (NIST, Sun, Microsoft, Boeing). Both backends are scored against the same shared denominator — +ns:N instances mean N cases where that backend couldn’t compile the schema (so it never got to attempt validation); they count against the backend that produced them rather than being silently dropped.

dimensionnSupXMLlibxml2
schemaTest (1.0)1432898.9%92.9% +3 timeouts
instanceTest (1.0)2509298.8% +78 ns98.3% +226 ns
schemaTest (1.1)109662.7%45.5%
instanceTest (1.1)142247.1% +592 ns18.2% +1081 ns

SupXML leads libxml2 on every axis. The +110-case gap on instanceTest (1.0) and the +29-point gap on instanceTest (1.1) are headline-worthy; the 1.1 deltas reflect that libxml2 doesn’t implement XSD 1.1 at all and falls back to “schema didn’t compile” for most cases.

Wall-clock for the same corpus (completed cases only; timed-out cases excluded so a single pathological schema doesn’t dominate the totals):

backendschema compileinstance validatetotal
sup-xml1.74 s1.12 s2.86 s
libxml23.96 s *3 timeouts2.85 s*6.81 s 3 timeouts

That’s ~2.3× faster on schema compile, parity on validate, ~2.4× faster end-to-end on the same corpus, plus 3 Microsoft “particles” schemas (particlesZ012 / Z015 / Z020) where libxml2’s xmlSchemaCheckElementDeclComponent enters quadratic behaviour and hits the per-test 30 s timeout — SupXML compiles each in under a millisecond. Reproduce with:

Terminal window
cargo bench -p sup-xml-bench --bench xsts_compliance
cargo bench -p sup-xml-bench --bench xsts11_compliance

XPath 1.0 — correctness

Two corpora — a 87-test hand-curated spec baseline and libxml2’s own 327-test corpus (vendored at tests/assets/xpath-libxml2-corpus/):

corpusnSupXML strictSupXML compatlibxml2
Hand-curated spec baseline8787/87 (100%)87/87 (100%)
libxml2’s own corpus327327/327 (100%)325/327 (99.4%)312/327 (95.4%)

On its own test corpus libxml2 fails 15 expressions against the XPath 1.0 spec — bugs that we annotated in the bench’s spec-graded override table (exponent in number literals, decimal-only string() output for big numbers, number('-') should be NaN not -0, IEEE round-to- nearest-even). For migrations from libxml2 pipelines, SupXML exposes XPathOptions { libxml2_compatible: true } that closes 13 of the 15 cases by relaxing the lexer and matching libxml2’s bignum formatting. The remaining 2 are a real IEEE rounding bug in libxml2’s number parser that we deliberately do not replicate even in compat mode. Reproduce with:

Terminal window
cargo bench -p sup-xml-bench --bench xpath_compliance
cargo bench -p sup-xml-bench --bench xpath_libxml2_corpus

HTML parse — matched against html5ever and libxml2

SupXML’s HTML5 parser is built on html5ever; the comparison below uses the same fixture set as parse-XML. html5ever* is the same tokenizer driven into a no-op TreeSink (discards every node) — a calibration baseline showing how much of SupXML’s runtime is sink overhead vs html5ever’s own work.

fixturesup-xmlhtml5ever*libxml2sx vs lxsx vs h5e*
hn48 MB/s46 MB/s49 MB/s0.97×1.03×
mdn_table69 MB/s68 MB/s81 MB/s0.85×1.01×
bbc_news140 MB/s135 MB/s113 MB/s1.24×1.04×
github_rust75 MB/s75 MB/s73 MB/s1.02×1.00×
wikipedia_ww271 MB/s71 MB/s67 MB/s1.07×1.01×
guardian118 MB/s114 MB/s119 MB/s0.99×1.04×
geomean (9 fixtures)1.02×1.01×

Median geomean against libxml2 is ~1.02× in SupXML’s favour; matched-contract head-to-head HTML throughput is essentially at parity with both libxml2 (whose HTML parser is HTML4-era and laxer) and a no- sink html5ever calibration baseline. The full HTML methodology is in the migrating-from-libxml2 guide. Reproduce with:

Terminal window
cargo bench -p sup-xml-bench --bench html_parse

Reproducing locally

Terminal window
# Clone
git clone https://github.com/SupsoOrg/sup-xml
cd sup-xml
# Full bench suite (~30 minutes)
cargo bench -p sup-xml-bench
# A specific bench (each is independent)
cargo bench -p sup-xml-bench --bench head_to_head
cargo bench -p sup-xml-bench --bench xsts_compliance
cargo bench -p sup-xml-bench --bench xpath_libxml2_corpus
cargo bench -p sup-xml-bench --bench html_parse

Bench inventory

Bench fileWhat it measures
head_to_head.rsParse throughput vs libxml2, quick-xml, roxmltree, xml-rs (matched contract)
mini.rsFast smoke bench across 12 parser configurations
parse.rsCriterion-style parse throughput
in_place.rs / in_place_vs_expat.rsDestructive in-place parsing vs Expat SAX
html_parse.rsHTML5 parse throughput vs html5ever and libxml2
stream.rs / stream_libxml2.rsStreaming parse throughput
xpath_compliance.rsXPath 1.0 hand-curated spec baseline
xpath_libxml2_corpus.rsXPath 1.0 conformance on libxml2’s own corpus
xsd.rsXSD validation micro-throughput
xsts_compliance.rsXSD 1.0 W3C test-suite pass rate + wall-clock
xsts11_compliance.rsXSD 1.1 W3C test-suite pass rate
xmlts_compliance.rsXML 1.0 not-wf conformance
exslt_head_to_head.rsEXSLT function throughput
recovery_check.rsRecovery-mode round-trip checks
libxml2_recovery_inspector.rsObserves libxml2’s recovery behaviour for diffing
event_counts.rsSAX event counts, validates contract parity

Methodology

The harness enforces a matched contract — all parsers under test must validate UTF-8, reject malformed structure, resolve the five predefined entities, and normalise attributes per XML 1.0 § 3.3.3. Parsers that expose only a looser contract (e.g. quick-xml with check_end_names: false) get an asterisk on the comparison and a note in crates/bench/benches/head_to_head.rs documenting what flags were flipped.

XSD wall-clock numbers exclude per-case timeouts so a single pathological schema can’t dominate the totals; the timeout count is surfaced inline as *N timeouts. XSTS conformance percentages use a shared denominator across backends so backends with more schema- compile failures can’t flatter their headline percentage by silently shrinking their own denominator.