What we measure first

  • Time to interactiveOpen or stream until the first row is usable — sections 1–5 below.
  • Browser memoryperformance.memory delta after parse vs NxsReader — section 17.
  • Filter & scan latencyPredicate and aggregate passes — sections 12–16.
  • Raw throughputWarm random access and WAL reference charts — sections 8–11 and 19.
What this page tests Eighteen browser workloads plus WAL reference charts. Row .nxb plus records_<N>_columnar.nxb for aggregate comparisons. Use size presets or upload matching fixtures.

pre-parsed on JSON/CSV bars — full parse happened before the timer. lazy decode on NXS row bars — reads from .nxb bytes inside the timed region. Purple columnar bars use records_<N>_columnar.nxb (colSumF64).

§1 Open file · n=10,000
Upload your own fixtures (same record counts):
No files selected — using size buttons above.
records_10000 .nxb row 1.24 MB (88% of JSON) .nxb col 868.4 KB (60% of JSON) .json 1.41 MB .csv 686.8 KB (47% of JSON)

1. Open file — parse the entire structure

JSON and CSV eagerly parse every byte; NXS reads only the header + tail-index.

JSON.parse
7.75 ms
parseCsv
12.10 ms
new NxsReader
49.7 µs

2. Cold first field — bytes already in memory

Parse/open then read one field at record n/2. Purple prefetch bar: prefetch_viewport(0, min(49,n−1)) then cursor(k) (Workload F1 pattern, in-memory).

3. Cold pipeline — fetch + parse + one field

Models the page-load path after fetch(): open or parse, then first field at n/2. Includes the same prefetch_viewport bar as §2.

4. Cold pipeline — open + sum score

No pre-parsed state: parse entire file then reduce (realistic analytics cold start).

5. Stream — time to first record

Chunked NxsStreamReader vs JSON.parse until first row is usable.

6. Open + iterate all records

End-to-end: open the format, then walk every record and read username once. Includes open + prefetch_viewport(0,49) + scan (virtual-scroll warm path).

7. JSON raw scan vs parse + loop

Substring scan for "username": lengths without JSON.parse vs full parse + loop vs NXS scan.

8. Iterate only — warm structures · JSON/CSV pre-parsed

Data already parsed/opened; walk every record and read username. Includes a reader with prefetch_viewport(0, min(49,n−1)) held warm across iterations.

9. Random-access read — one field from record k · JSON/CSV pre-parsed

JSON/CSV: property access on a pre-parsed array. NXS: decode from row .nxbrecord vs cursor vs buildFieldIndex (optional WASM build).

10. Random-access read — multiple fields from record k · JSON/CSV pre-parsed

Four fields per random record. NXS seekWarm(k) amortises the rank cache when reading several fields on the same row.

11. Scattered access — strided indices · JSON/CSV pre-parsed

~500 reads at evenly spaced record indices (simulates non-sequential UI paging).

12. Full scan — four fields per record

Open + linear pass reading username, age, balance, active on every row. NXS uses zero-alloc cursor.scan (not seekWarm per row).

13. Filter — count where score > 80 · JSON/CSV pre-parsed

Predicate over all records: JSON loop vs NXS cursor.seek + getF64BySlot. Row layout walks the bitmask per field — use columnar layout for filter-heavy analytics.

14. Aggregate — sum of 'score' (warm) · JSON/CSV pre-parsed

Pre-parsed JSON loop, raw CSV column scan, row sumF64 (WASM when loaded), and columnar colSumF64 when the columnar fixture is present.

15. Indexed sum vs column reducer · row vs columnar layout

Row: buildFieldIndex("score") + loop vs sumF64. Columnar: dense colSumF64 (SIMD path in native drivers).

16. Columnar aggregate vs JSON · §7.4 / analytics headline

Sum score at the same record count: JSON warm (pre-parsed) vs JSON cold (parse each iteration) vs NXS columnar cold, mistaken prefetch (new reader each time), and persistent reader with prefetch_column once. Requires records_<N>_columnar.nxb and matching JSON.

NXS cold (new reader each iteration) beats pre-parsed warm JSON by ~10% without any warmup. NXS warm (persistent reader + prefetch_column) beats pre-parsed warm JSON by ~45%. The prefetch_column + sum (new reader) bar shows the cost of calling prefetch without retaining the reader — avoid that pattern.

17. Memory — heap growth (Chrome)

Indicative performance.memory delta after parse vs NxsReader (not file size on disk).

18. Workers — parallel sum score

Main-thread sumF64 vs four module workers each summing a chunk (buffer copy per worker).

19. WAL span ingestion — append · recover · seal · roundtrip

Rust cargo run --release --bin bench on Apple M-series (tmpfs I/O for append). Reference charts below; values match BENCHMARK.md. Append is amortised append-batch (encode + write all spans per run).

1,000 spans WAL 113.4 KB (64.7% of JSON) Sealed 120.8 KB (68.9% of JSON) JSON 175.3 KB

Timings (ns / span, lower is better)

append-batch
1.6 µs / span
recover
1.2 µs / span
seal
3.5 µs / span
roundtrip
6.1 µs / span

File size vs JSON NDJSON baseline

WAL (.nxsw)
113.4 KB (64.7%)
Sealed (.nxb)
120.8 KB (68.9%)
JSON NDJSON
175.3 KB (100.0%)

Honest tradeoffs

Nyxis is not a drop-in replacement for your entire data stack. Published comparisons acknowledge where other formats win.

  • Apache Arrow remains superior for dense analytical scans — use NXS columnar layout or the Arrow bridge for hybrid pipelines.
  • FlatBuffers / Cap'n Proto may cold-open faster on tiny warm-cache files — see Workload B.
  • Parquet remains excellent warehouse storage — NXS targets transport, browser UIs, and streaming ingest.
  • JSON (warm) can tie NXS on simple field access when the entire document is already parsed — bars label pre-parsed explicitly.

Reproducibility

Re-run the same workloads locally or in CI.

  • Public fixturesnyxis/site/bench/fixtures/records_*.nxb (+ matching .json / .csv)
  • Browser harness — this page (site/web/src/demos/bench-page.js)
  • Native benchescargo run --release --bin bench in nyxis
  • Methodology & hardwareBENCHMARK.md (Apple Silicon dev hosts, Linux x86_64, EPYC AVX-512 rows)
  • Generate fixturesmake -C nyxis bench-fixtures (see repo Makefile)

19. WAL encoder — cross-language comparison

Per-span append throughput across all ten implementations, measured at 10,000 spans on Apple M-series (arm64). NXS encodes directly to the binary format; JSON baseline uses each language's standard library serialiser. JS numbers are from the live benchmark above.

NXS WAL append (ns/span, lower is better)

C
73 ns / span (13699k/s)
Go
131 ns / span (7634k/s)
Rust
131 ns / span (7634k/s)
JS (fast)
250 ns / span (4000k/s)
Ruby (C ext)
336 ns / span (2976k/s)
JS (WASM)
375 ns / span (2667k/s)
Python (C ext)
438 ns / span (2283k/s)
JS (generic)
750 ns / span (1333k/s)
Python (pure)
3.8 µs / span (263k/s)
Ruby (pure)
5.3 µs / span (189k/s)

JSON serialise (ns/span, lower is better)

Rust
131 ns / span (7634k/s)
C
270 ns / span (3704k/s)
Go
301 ns / span (3322k/s)
JS (fast)
320 ns / span (3125k/s)
JS (WASM)
320 ns / span (3125k/s)
JS (generic)
320 ns / span (3125k/s)
Ruby (C ext)
383 ns / span (2611k/s)
Ruby (pure)
383 ns / span (2611k/s)
Python (C ext)
1.4 µs / span (723k/s)
Python (pure)
1.4 µs / span (723k/s)