Benchmarks
StAX-XML benchmarks the package as a pure JavaScript parser. The latest
documentation and the v1.0.0-rc3 release benchmark set use this same contract:
measure the public JavaScript surfaces that users actually call, with full
JavaScript string and object materialization.
What to Run
Section titled “What to Run”pnpm --dir packages/benchmark bench:syncmeasures synchronous parser and builder paths.pnpm --dir packages/benchmark bench:asyncmeasures async parser and writer paths.pnpm --dir packages/benchmark bench:runtime-matrixcompares the same JavaScript reader workload across Node, Bun, and Deno when those runtimes are installed.pnpm --filter benchmark run release:expandedregenerates the release runtime matrix, the 4 GiBStreamReaderSyncindex-first benchmark, and the converter compiled batch-plan benchmark, plus the 1 GiB writer benchmark artifact.
Measurement Scope
Section titled “Measurement Scope”Parser benchmarks return JavaScript strings and JavaScript objects. They do not count raw-byte traversal as a completed parse. Large-file checks focus on low RSS, staying below JavaScript string-size limits, and pull-style consumption through the stream, event, and converter APIs.
Release Results
Section titled “Release Results”The current release set was refreshed on Node 24.15.0, Bun 1.3.13, and Deno 2.7.13. Runtime rows use a generated 16 MiB fixture, 1 warmup, and 3 measured runs.
| Runtime | Workload | Throughput | Average | Heap delta |
|---|---|---|---|---|
| Node 24.15.0 | public-sync-full-string | 96.1 MiB/s | 166.50 ms | 24.6 MiB |
| Node 24.15.0 | stream-sync-index-full-string | 105.5 MiB/s | 151.66 ms | 23.3 MiB |
| Bun 1.3.13 | public-sync-full-string | 69.2 MiB/s | 231.12 ms | 16.8 MiB |
| Bun 1.3.13 | stream-sync-index-full-string | 138.7 MiB/s | 115.36 ms | 67.2 MiB |
| Deno 2.7.13 | public-sync-full-string | 93.3 MiB/s | 171.51 ms | 27.5 MiB |
| Deno 2.7.13 | stream-sync-index-full-string | 111.2 MiB/s | 143.94 ms | 27.8 MiB |
The 4 GiB generated stream fixture uses StreamReaderSync over
Iterable<Uint8Array[]>, consumes each batch with eventCount plus index
accessors, skips warmup, and measures 3 runs.
| Workload | Average | Throughput | Events | Average RSS delta |
|---|---|---|---|---|
StreamReaderSync index-first, 4 GiB | 43.44 s | 94.29 MiB/s | 572,662,314 | 4.0 MiB |
Other Benchmark Tracks
Section titled “Other Benchmark Tracks”The release matrix is only one part of the benchmark suite. Parser, stream reader, converter, builder, writer, async writer, and large writer sink benchmarks remain separate measurement surfaces:
| Track | Command | What it measures |
|---|---|---|
| Parser fixture series | pnpm --dir packages/benchmark bench:sync | 2 KiB, 4 KiB, 13 MiB, and 98 MiB parser fixtures plus sync builder rows. |
| Builder small/large | pnpm --dir packages/benchmark bench:builder:small and bench:builder:big | XML emission from builder-friendly intermediate data. |
| Converter compiled batch plan | pnpm --dir packages/benchmark bench:converter:compiled-batch | Manual StreamReaderSync projection vs converter auto-lowered and explicit .compile() batch dispatch plans. |
| Async parser/writer | pnpm --dir packages/benchmark bench:async | Async parser and async-vs-sync writer API overhead. |
| Writer core | pnpm --dir packages/benchmark bench:writer:core | Direct writer method throughput for compact and pretty output. |
| 1 GiB writer | pnpm --dir packages/benchmark bench:writer:1gb:json | Async writer and sync sink writer over memory and temp-file targets. |
For large XML output, WriterSyncSink remains the recommended synchronous path
when the caller wants incremental writes without retaining the whole XML string.
The release artifact keeps the 1 GiB writer result under
packages/benchmark/results/release/raw/writer-1gb.json.
Converter API Comparison
Section titled “Converter API Comparison”The converter comparison is part of the pure JavaScript release contract. It
uses a manual StreamReaderSync baseline built from batches with eventCount
plus index accessors. The converter rows parse the same byte fixture through:
schema.parseSync(bytes), which auto-lowers supported schemas to the compiled dispatch batch plan.schema.compile().parseSync(bytes), which makes the same compiled batch plan explicit and reusable.
Both converter rows are verified against the manual projection before timing.
The release artifact is
packages/benchmark/results/release/converter-compiled-batch-plan.json.
| Case | Throughput | Average | Books | Checksum |
|---|---|---|---|---|
Manual StreamReaderSync projection | 80.17 MiB/s | 199.58 ms | 139,458 | -1845341048 |
Converter schema.parseSync(bytes) | 47.11 MiB/s | 339.63 ms | 139,458 | -1845341048 |
Converter schema.compile().parseSync(bytes) | 46.03 MiB/s | 347.61 ms | 139,458 | -1845341048 |
StreamReaderSync Consumption
Section titled “StreamReaderSync Consumption”For large byte input, prefer batch-local index access:
import { StreamEventType, StreamReaderSync } from 'stax-xml';
const reader = new StreamReaderSync(byteBatches);
for (const batch of reader) { for (let index = 0; index < batch.eventCount; index++) { if (batch.typeAt(index) === StreamEventType.START_ELEMENT) { const name = batch.nameAt(index); const attributeCount = batch.attributeCountAt(index);
for (let attribute = 0; attribute < attributeCount; attribute++) { const attributeName = batch.attributeNameAt(index, attribute); const attributeValue = batch.attributeValueAt(index, attribute); consume(name, attributeName, attributeValue); } } }}for (const event of batch) is still supported, but it allocates wrapper event
objects. On the canonical 98 MiB fixture, index-first consumption was 730.56 ms
with about 39.8 MiB heap delta, while wrapper iteration was 1.28 s with about
482.7 MiB heap delta.