perf: Int32Array slab storage for consumer mappings#71
Merged
Conversation
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.
Summary
Consumer-side counterpart to the generator's MappingList refactor (#64).
BasicSourceMapConsumer/IndexedSourceMapConsumerpreviously held ~350k JS objects per parse for a typical bundle (one per mapping), which dominated init time (_parseMappingswas 76% of init) and drove a ~12% GC tax. This PR moves both__generatedMappingsand__originalMappingsto flat Int32Array slabs with the same 6-field layout the generator already uses.Hot-path lookups read the slab directly —
originalPositionForandgeneratedPositionFornow binary-search through typed-array slots, with the existing warm caches and inline GLB/LUB code paths (PRs #68 + previous) ported to slab access.eachMappingkeeps the post-#70 stable-shape object iteration via the back-compat_generatedMappings/_originalMappingsgetters, which lazily materialize an object array from the slab on first access and cache it.Results (bench-diff vs main, SOLO)
babel.min.js.map
vscode.map (45MB, ~1.4M segments)
Design notes
SLAB_FIELDS = 6with the same field order and the same-1sentinel.fromSourceMapcopies the generator's_bufinto the consumer's_genBufwith a single typed-arrayset()and no per-row decoding._buildOriginalMappingsbuckets gen-slab row indices by source, sorts each bucket by (originalLine, originalColumn) — well-formed maps emit per-source in original-position order so the check-sorted pass almost always wins — then copies rows into_origBuf. A parallel_origToGenInt32Array maps orig-row → gen-row socomputeColumnSpans/allGeneratedPositionsFor/generatedPositionForcan look uplastGeneratedColumnfrom the_lastGenColsInt32Array.computeColumnSpanspopulates_lastGenCols(Int32Array sized to_genCount); the materialized-object cache is invalidated so subsequent_generatedMappingsgetter access picks up the newlastGeneratedColumnfield._generatedMappings/_originalMappingsgetter, which lazily materializes a back-compat object array (with the stable hidden class from perf: stable Mapping hidden class in _parseMappings #70) the first time it's accessed. Repeated eachMapping calls iterate that cached array — matching the post-perf: stable Mapping hidden class in _parseMappings #70 baseline. The slab is what makes opf/gpf/init fast; the object-array cache is what keeps eachMapping fast.originalPositionFor/generatedPositionForis now explicit at the entry of those methods —_findMapping(which previously held the throw) is gone since slab lookups are inline.Test plan
yarn test— 205/205 non-TODO tests pass; 8 pre-existing TODO failures unrelatedbench-diff.sh main traceon babel.min.js.map — mean +30.8%bench-diff.sh main traceon vscode.map — mean +26.7%