Skip to content

Port @mraleph optimizations - #2

Merged
7rulnik merged 7 commits into
patch-0.6.1from
apply-mraleph-optimizations
Feb 7, 2021
Merged

Port @mraleph optimizations #2
7rulnik merged 7 commits into
patch-0.6.1from
apply-mraleph-optimizations

Conversation

@7rulnik

@7rulnik 7rulnik commented Feb 7, 2021

Copy link
Copy Markdown
Owner

Moved from benthemonkey#1

I spotted the article Maybe you don't need Rust and WASM to speed up your JS written by @mraleph and decided to try to port non-hardcore optimizations.

I benchmarked it with node 14.15.4.

Before:

Parsing source map
iteration: 3.590s
iteration: 3.306s
iteration: 3.459s
iteration: 3.546s
iteration: 3.379s
iteration: 3.562s
iteration: 3.319s
[Stats samples: 7, total: 24158 ms, mean: 3451.1428571428573 ms, stddev: 128.1444497432487 ms]

After:

Parsing source map
iteration: 1.030s
iteration: 1.769s
iteration: 1.104s
iteration: 1.118s
iteration: 1.189s
iteration: 1.027s
iteration: 1.104s
iteration: 1.254s
iteration: 1.041s
iteration: 1.060s
iteration: 1.140s
iteration: 1.103s
iteration: 1.124s
iteration: 1.092s
iteration: 1.098s
iteration: 1.083s
iteration: 1.068s
iteration: 1.129s
iteration: 1.067s
iteration: 1.129s
iteration: 1.090s
iteration: 1.094s
[Stats samples: 22, total: 24913 ms, mean: 1132.409090909091 ms, stddev: 154.70478799497909 ms]

@7rulnik

7rulnik commented Mar 21, 2024

Copy link
Copy Markdown
Owner Author

Applied optimizations:

  • Optimizing Sorting - Argument Adaptation
  • Optimizing Sorting - Monomorphisation
  • Optimizing Parsing - Removing Segment Cache
  • Optimizing Sorting - Algorithmic Improvements

TO DO:

  • Optimizing Parsing - Reducing GC Pressure
  • Optimizing Parsing - Using Uint8Array Instead of a String.

7rulnik added a commit that referenced this pull request May 10, 2026
…eMap (#64)

* perf: typed-array MappingList — slab storage + slab-direct serializer + applySourceMap rebuild

Replace lib/mapping-list.js's `Array<{...mappingObject}>` backing store with
an Int32Array slab. 6 i32 slots per mapping (genLine, genCol, srcIdx,
origLine, origCol, nameIdx), -1 sentinel for "no value". Source/name
strings live in the owning SourceMapGenerator's ArraySets; MappingList
stores the resolved indices so the serializer doesn't need a per-mapping
`indexOf` (which previously hit the ArraySet's Map.get even after the
single-slot indexOf cache from #58).

Per bench-data-followups #2 — generator memory ratio ≈ generator speed
ratio. The win is heap-pressure / GC / cache: 350k mapping objects per
addMapping cycle becomes 350k * 24 bytes contiguous slab.

`MappingList.add(genLine, genCol, origLine, origCol, srcIdx, nameIdx)`
takes positional integer args. `SourceMapGenerator.addMapping` resolves
sources/names through the existing ArraySets (which addMapping was already
calling) and passes the integer index in. No more `{ ... }` object literal
allocation per addMapping.

`SourceMapGenerator.applySourceMap` no longer mutates mapping objects
through the unsortedForEach callback — that pattern relied on the callback
receiving the actual stored reference, which slab storage can't provide.
The rewrite walks the old slab via the F_* constants, applies the
transformation, and emits into a fresh MappingList bound to the new
sources/names ArraySets, then swaps all three at once.

`SourceMapGenerator._serializeMappings` reads the slab directly instead of
toArray(). Source/name fields stored in the slab are already the int
indices needed for serialization (resolved when added) — no per-mapping
`indexOf` lookup. The compareByGeneratedPositionsInflated dedup check is
replaced with `ml._equalsPrev(i)` — a slab method that does six i32
equality checks. Same equality classes as the old check (same srcIdx ⇔
same source string after interning) so the dedup still works.

`unsortedForEach` and `toArray` remain API-compatible by materializing
JS objects on demand from the slab. Internal hot paths bypass them.

* perf: BasicSourceMapConsumer.fromSourceMap reads MappingList slab directly

Drop the `aSourceMap._mappings.toArray().slice()` materialization. The
generator's MappingList already stores source/name as integer indices
into its _sources / _names ArraySets, and the new consumer's _sources /
_names were just initialized from the same toArray() output — so the
indices are identical and no per-mapping `indexOf` is needed.

Reads i32 fields straight from `ml._buf` via the F_* layout constants
exported from `lib/mapping-list.js`.

* test: coverage for typed-array MappingList and slab paths

- test/internal/test-mapping-list.js (new): unit tests for the MappingList
  surface — unsortedForEach / toArray materialization, the 6-level
  sortedness cascade in `add`, the matching cascade in `_sort`'s
  comparator, the `_equalsPrev` dedup helper, and slab capacity growth.
- test/internal/test-source-map-consumer-internals.js: add a fromSourceMap
  test that exercises a source-less generated mapping (case-1 per
  _validateMapping) so the `srcIdx === -1` slab-read branch is covered.
- test/internal/test-util.js: pin direct genLine/genCol coverage for
  compareByGeneratedPositionsInflated. The function was previously
  covered transitively through MappingList.add's generatedPositionAfter;
  the new slab-backed MappingList doesn't call it, so those early-return
  branches need direct test coverage.

* test: cover applySourceMap with source-less mappings + drop unreachable origCol guard

Two coverage holes flagged by the per-branch lcov:
- lib/source-map-generator.js:253 — the `srcIdx === -1 ? null : ...`
  ternary's `null` arm only fires when applySourceMap walks a source-less
  mapping, and existing applySourceMap tests are all fully-sourced.
- lib/source-map-generator.js:259 — `origCol === -1 ? 0 : origCol` was
  unreachable. addMapping sets `origLine` and `origCol` together, so
  inside the `origLine !== -1` branch above, `origCol === -1` never
  fires. Dropping the guard removes the dead branch.

Adds an applySourceMap test in test-source-map-consumer-internals.js
that feeds the outer generator a source-less mapping alongside a
fully-sourced one and asserts the source-less mapping passes through
unchanged while the sourced one transforms.

Bumps all-files branch coverage 96.94 → 97.21 (above the 97 threshold).
source-map-generator.js is now 100/100/100.
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