Skip to content

perf: speed up VLQ mappings decode#393

Merged
Boshen merged 1 commit into
mainfrom
perf-vlq-decode
Jul 2, 2026
Merged

perf: speed up VLQ mappings decode#393
Boshen merged 1 commit into
mainfrom
perf-vlq-decode

Conversation

@Boshen

@Boshen Boshen commented Jul 2, 2026

Copy link
Copy Markdown
Member

Problem

Profiling parse of a ~115 KB sourcemap (samply + inline-expanded symbolication on the criterion bench) attributed ~40% of parse time to the VLQ mappings decode loop: per-byte delimiter compares before the table lookup, per-value array bounds checks, shift bookkeeping for every value, and a branchy sign decode. (The other big buckets — serde_json escaped-string unescaping and allocator traffic — are inherent to the JSON layer; the previously suspected token-vec shrink measured only ~1.2%.)

Changes

Rework parse_vlq_segment_into internals; its signature and all validation in decode_mapping are untouched:

  • Fold , / ; into the decode table as negative sentinels → one table load per byte instead of two compares plus a load. Bytes outside the base64 alphabet map to 63, reproducing the historical lenient behavior exactly (they already decoded as payload 31 + continuation, i.e. like /).
  • Single-byte fast path per value (small deltas dominate real mappings: ~80% of values are one byte), skipping continuation/shift bookkeeping and the overflow guard.
  • Branchless sign-magnitude decode — delta signs are poorly predictable in real mappings, so the old data-dependent branch mispredicted often.

Behavior is locked, not just tested

  • New unit tests written against the old implementation first: error precedence (VlqLeftover beats field validation, column check beats size check), exact >5-field counts, invalid-char ≡ / equivalence.
  • Differential harness over all 142 .map/.json files under tests/ (fixtures + tc39 resources): decoded tokens, re-encoded bytes, and error variants are byte-identical old vs new.
  • tc39 spec suite: identical pass/fail sets (86 passed, same 11 pre-existing failures).
  • Full test suite + clippy --all-targets --all-features clean.

Benchmarks (criterion vs saved baseline, M-series)

bench before after Δ
parse/real_xlarge 126.8 µs 109.7 µs −13.6%
parse/real_large 3.82 µs 3.23 µs −15.3%
parse/real_medium 592 ns 547 ns −7.2%
smoke (inline parse) 548 ns 509 ns −12.3%
serialize/* , lookup_table/* , concat/* parity

Companion PR (independent, encoder side): fixes a latent unreserved first-token write in serialize_mappings.

@codspeed-hq

codspeed-hq Bot commented Jul 2, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 1.6%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 6 improved benchmarks
✅ 10 untouched benchmarks
⏩ 7 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
parse[real_small] 11.9 µs 11.6 µs +2.42%
from_json_string_inline 14.4 µs 14.1 µs +2.24%
parse[real_xlarge] 1.3 ms 1.2 ms +1.43%
parse[real_medium] 14.7 µs 14.5 µs +1.38%
parse[real_large] 47.2 µs 46.6 µs +1.14%
build_single 5.8 µs 5.8 µs +1.01%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing perf-vlq-decode (a193fad) with main (1423083)

Open in CodSpeed

Footnotes

  1. 7 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Profiling parse of a ~115KB sourcemap (samply, inline-expanded) attributed
~40% of parse time to the VLQ segment loop. Rework its internals, keeping
`parse_vlq_segment_into`'s signature and all validation in `decode_mapping`
untouched:

- Fold the `,` / `;` delimiters into the decode table as negative
  sentinels: one table load per byte instead of two compares plus a load.
  Bytes outside the base64 alphabet map to 63, which reproduces the
  historical lenient behavior exactly (they already decoded as payload 31
  with the continuation bit set, i.e. like '/').
- Add a single-byte fast path per value (small deltas dominate real
  mappings), skipping continuation/shift bookkeeping entirely.
- Decode the sign-magnitude bit branchlessly.

Error semantics are locked by new unit tests written against the old
implementation first (error precedence, exact >5-field counts,
invalid-char equivalence), and a differential run over all 142 fixture /
tc39 resource files shows byte-identical tokens, re-encoded output, and
error variants.

Criterion vs saved baseline: parse/real_xlarge -13.6% (126.8us -> 109.7us),
parse/real_large -15.3%, parse/real_medium -7.2%, smoke -12.3%. Other
groups at parity.
@Boshen Boshen force-pushed the perf-vlq-decode branch from 8425c7f to a193fad Compare July 2, 2026 12:06
@Boshen Boshen marked this pull request as ready for review July 2, 2026 12:07
@Boshen Boshen merged commit d7b6de0 into main Jul 2, 2026
8 checks passed
@Boshen Boshen deleted the perf-vlq-decode branch July 2, 2026 12:07
@oxc-guard oxc-guard Bot mentioned this pull request Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant