perf: speed up VLQ mappings decode#393
Merged
Merged
Conversation
Merging this PR will improve performance by 1.6%
|
| 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)
Footnotes
-
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. ↩
361765e to
8425c7f
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_intointernals; its signature and all validation indecode_mappingare untouched:,/;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 to63, reproducing the historical lenient behavior exactly (they already decoded as payload 31 + continuation, i.e. like/).Behavior is locked, not just tested
VlqLeftoverbeats field validation, column check beats size check), exact >5-field counts, invalid-char ≡/equivalence..map/.jsonfiles undertests/(fixtures + tc39 resources): decoded tokens, re-encoded bytes, and error variants are byte-identical old vs new.clippy --all-targets --all-featuresclean.Benchmarks (criterion vs saved baseline, M-series)
Companion PR (independent, encoder side): fixes a latent unreserved first-token write in
serialize_mappings.