|
17 | 17 | - [Using Custom Serializers or Deserializers](#using-custom-serializers-or-deserializers) |
18 | 18 | - [Overriding built-in Container Types](#overriding-built-in-container-types) |
19 | 19 | - [Performance](#performance) |
| 20 | + - [Real-World Throughput](#real-world-throughput) |
20 | 21 | - [Comparison to JavaScript](#comparison-to-javascript) |
| 22 | + - [Library Comparison](#library-comparison) |
| 23 | + - [Lazy Fields](#lazy-fields) |
21 | 24 | - [Performance Tuning](#performance-tuning) |
22 | | - - [Fast-Path Compatibility Matrix](#fast-path-compatibility-matrix) |
23 | | - - [Container Compatibility Matrix](#container-compatibility-matrix) |
24 | 25 | - [Running Benchmarks Locally](#running-benchmarks-locally) |
25 | 26 | - [Debugging](#debugging) |
26 | 27 | - [Architecture](#architecture) |
@@ -505,79 +506,103 @@ This same pattern works for subclassable built-ins like `Array`, `Map`, `Set`, a |
505 | 506 |
|
506 | 507 | ## Performance |
507 | 508 |
|
508 | | -The `json-as` library is engineered for **multi-GB/s processing speeds**, leveraging SIMD and SWAR optimizations along with highly efficient transformations. The charts below highlight key performance metrics such as build time, operations-per-second, and throughput. |
| 509 | +`json-as` is engineered for **multi-GB/s** serialization and deserialization. Every `@json` schema is compiled to specialized WebAssembly at build time, and bytes are scanned by one of three interchangeable backends: |
509 | 510 |
|
510 | | -### Comparison to JavaScript |
| 511 | +- **NAIVE** — a portable, branchy scalar scanner. The correctness baseline; needs no special CPU features. |
| 512 | +- **SWAR** — *SIMD-Within-A-Register*: processes 8 bytes at a time with ordinary 64-bit integer math. The default. |
| 513 | +- **SIMD** — true 128-bit vector scanning. Fastest on large and string-heavy payloads; enable with `--enable simd`. |
511 | 514 |
|
512 | | -The following charts compare JSON-AS against JavaScript's native `JSON` implementation. It's as fair as possible and runs on V8's turboshaft optimizer. The published charts are generated locally and pushed to the `docs` branch. |
| 515 | +The mode is chosen per build via `JSON_MODE` (see [Performance Tuning](#performance-tuning)). Orthogonal to the scan mode, the generated **struct** path can be swapped for **[lazy](#lazy-fields)** fields (defer parsing until first access) or the fully dynamic, schema-less **`JSON.Obj`** path. |
513 | 516 |
|
514 | | -> Note: Benchmarks reflect the **latest version**. Older versions may show different performance. |
515 | | -> |
516 | | -> Current local benchmark machine: Apple M4 Max (16 cores - 12 performance + 4 efficiency), 64 GB RAM, macOS 26. |
| 517 | +> All figures below are **end-to-end**: deserialization includes allocating the destination object/array, not just scanning bytes — raw parser throughput is higher. Charts are generated locally and pushed to the [`docs`](https://github.com/JairusSW/json-as/tree/docs) branch, and reflect the **latest release** (older versions may differ). |
517 | 518 | > |
518 | | -> Benchmark results include normal end-to-end work such as allocating the destination object or array before deserializing into it. Raw parser throughput is higher than the published figures because these numbers intentionally include that allocation/setup cost. |
| 519 | +> Benchmark machine: AMD Ryzen 7 7800X3D (8 cores, 96 MB 3D V-Cache), 32 GB RAM, Zorin OS 18.1 (Linux 6.17). JavaScript baselines run on V8's turboshaft optimizer. |
| 520 | +
|
| 521 | +📊 **[Browse the full chart set for this release →](https://github.com/JairusSW/json-as/tree/docs/charts/v1.5.0)** |
| 522 | + |
| 523 | +### Real-World Throughput |
| 524 | + |
| 525 | +The headline benchmark: nine standard JSON payloads — drawn from the [yyjson](https://github.com/ibireme/yyjson) and [`nativejson-benchmark`](https://github.com/miloyip/nativejson-benchmark) corpora — measured in all three scan modes, with the SIMD lazy-struct and dynamic `JSON.Obj` paths shown alongside. |
| 526 | + |
| 527 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/classic-payload-deserialize-v8.svg" alt="Deserialization throughput across nine classic JSON payloads"> |
| 528 | + |
| 529 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/classic-payload-serialize-v8.svg" alt="Serialization throughput across nine classic JSON payloads"> |
| 530 | + |
| 531 | +Each payload stresses a different document shape: |
| 532 | + |
| 533 | +| Payload | Size | What it stresses | |
| 534 | +|---------|------|------------------| |
| 535 | +| **Twitter** | 467 KB | API response, fully-modeled struct schema with `@optional` keys | |
| 536 | +| **Canada** | 2.1 MB | GeoJSON — deeply nested arrays of floating-point coordinates | |
| 537 | +| **CITM** | 500 KB | Concert catalog — dynamic-key `Map`s plus uniform struct arrays | |
| 538 | +| **Poet** | 3.3 MB | ~8,900 flat `{desc, name, id}` records — pure struct fast path | |
| 539 | +| **GitHub** | 53 KB | 30 GitHub events — a wide union of per-event-type fields | |
| 540 | +| **GSOC** | 3.1 MB | ~1,264 org records keyed by id (schema.org JSON-LD `Map`) | |
| 541 | +| **Lottie** | 289 KB | Vector-animation doc — structs over deeply variable layer data | |
| 542 | +| **otfcc** | 66.4 MB | OpenType font dump — 15 tables captured as `JSON.Raw` | |
| 543 | +| **FGO** | 48.8 MB | Game-data dump — 193 irregular tables as `Map<string, JSON.Raw>` | |
| 544 | + |
| 545 | +The five series in each chart: |
519 | 546 |
|
520 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart01.svg" alt="Performance Chart 1"> |
| 547 | +- **NAIVE / SWAR / SIMD** — the generated struct path under each scan backend, parsing every field into a typed schema. |
| 548 | +- **Lazy (SIMD)** — `@json({ lazy: "auto" })`: each field's raw slice is stored at parse time and decoded only on first access; on serialize, untouched fields stream their original bytes straight back out. Reading or rewriting a subset is dramatically cheaper — see [Lazy Fields](#lazy-fields). |
| 549 | +- **JSON.Obj (SIMD)** — a fully dynamic, schema-less parse into `JSON.Obj`, with no generated per-type code. |
521 | 550 |
|
522 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart02.svg" alt="Performance Chart 2"> |
| 551 | +### Comparison to JavaScript |
| 552 | + |
| 553 | +`json-as` against JavaScript's native `JSON`, parsed and stringified in a fresh V8 — as close to apples-to-apples as a Wasm-vs-native comparison gets. |
| 554 | + |
| 555 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/overview-serialize.svg" alt="Performance Chart 1"> |
| 556 | + |
| 557 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/overview-deserialize.svg" alt="Performance Chart 2"> |
523 | 558 |
|
524 | 559 | <details> |
525 | 560 | <summary>String serialize charts (click to expand)</summary> |
526 | 561 |
|
527 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart03.png" alt="Performance Chart 3"> |
| 562 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/string-serialize.svg" alt="Performance Chart 3"> |
528 | 563 |
|
529 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart07.png" alt="Performance Chart 7"> |
| 564 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/string-serialize-1mb.svg" alt="Performance Chart 7"> |
530 | 565 | </details> |
531 | 566 |
|
532 | 567 | <details> |
533 | 568 | <summary>String deserialize charts (click to expand)</summary> |
534 | 569 |
|
535 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart04.png" alt="Performance Chart 4"> |
| 570 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/string-deserialize.svg" alt="Performance Chart 4"> |
536 | 571 |
|
537 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart08.png" alt="Performance Chart 8"> |
| 572 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/string-deserialize-1mb.svg" alt="Performance Chart 8"> |
538 | 573 | </details> |
539 | 574 |
|
540 | 575 | <details> |
541 | 576 | <summary>Object serialize charts (click to expand)</summary> |
542 | 577 |
|
543 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart05.png" alt="Performance Chart 5"> |
| 578 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/object-serialize.svg" alt="Performance Chart 5"> |
544 | 579 |
|
545 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart09.png" alt="Performance Chart 9"> |
| 580 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/object-serialize-1mb.svg" alt="Performance Chart 9"> |
546 | 581 | </details> |
547 | 582 |
|
548 | 583 | <details> |
549 | 584 | <summary>Object deserialize charts (click to expand)</summary> |
550 | 585 |
|
551 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart06.png" alt="Performance Chart 6"> |
| 586 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/object-deserialize.svg" alt="Performance Chart 6"> |
552 | 587 |
|
553 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart10.png" alt="Performance Chart 10"> |
| 588 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/object-deserialize-1mb.svg" alt="Performance Chart 10"> |
554 | 589 | </details> |
555 | 590 |
|
556 | 591 | <details> |
557 | 592 | <summary>Primitive (de)serialize charts (click to expand)</summary> |
558 | 593 |
|
559 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart11.svg" alt="Primitive serialization performance"> |
560 | | - |
561 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart12.svg" alt="Primitive deserialization performance"> |
562 | | -</details> |
563 | | - |
564 | | -<details> |
565 | | -<summary>Real-world payloads — eager / lazy / dynamic (click to expand)</summary> |
566 | | - |
567 | | -Throughput across nine classic JSON payloads (Twitter, Canada, CITM, Poet, GitHub events, GSOC, Lottie, otfcc, FGO), each in the NAIVE / SWAR / SIMD scan modes, with the SIMD lazy-struct and dynamic `JSON.Obj` paths shown alongside. |
568 | | - |
569 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/classic-payload-deserialize-v8.png" alt="Classic payload deserialize throughput"> |
| 594 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/primitive-serialize.svg" alt="Primitive serialization performance"> |
570 | 595 |
|
571 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/classic-payload-serialize-v8.png" alt="Classic payload serialize throughput"> |
| 596 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/primitive-deserialize.svg" alt="Primitive deserialization performance"> |
572 | 597 | </details> |
573 | 598 |
|
574 | | -### Library comparison |
| 599 | +### Library Comparison |
575 | 600 |
|
576 | 601 | How `json-as` stacks up against other JSON libraries on a ~5 KiB GitHub-repo payload: JavaScript's native `JSON` and `fast-json` (each in a fresh V8), plus the `assemblyscript-json` package. The `json-as` bars (generated struct, lazy struct, and dynamic `JSON.Obj`) are averaged across the NAIVE / SWAR / SIMD scan modes. |
577 | 602 |
|
578 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart14.png" alt="Library comparison - deserialize throughput"> |
| 603 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/library-deserialize.svg" alt="Library comparison - deserialize throughput"> |
579 | 604 |
|
580 | | -<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/chart13.png" alt="Library comparison - serialize throughput"> |
| 605 | +<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/v1.5.0/library-serialize.svg" alt="Library comparison - serialize throughput"> |
581 | 606 |
|
582 | 607 | ### Lazy Fields |
583 | 608 |
|
|
0 commit comments