Commit 5deffad
committed
perf(compile): build dispatch handler context as a stable-shape literal
The compile loop built each handler's this-binding with
Object.assign({sliceSerialize: ...}, context). Object.assign goes
through a generic copy-loop over the source's enumerable properties;
V8 cannot give that loop a stable hidden class for the merged object
because the source's shape is a runtime variable. Each event paid the
cost of a copy-iteration plus a hidden-class transition.
The fix replaces the Object.assign with an inline object literal that
lists every field explicitly. The literal has a fixed compile-time
shape so V8 keeps a single hidden class for the merged object across
every event, allocates it through a fast path, and can inline the
construction at the call site. The merged object is still allocated
per event, so the prior contract (handlers may capture `this` or
reassign top-level fields without leaking into subsequent events) is
preserved. The dispatch is otherwise unchanged.
Inputs that benefit, with multi-run median-of-medians vs the baseline
(spread in parentheses):
10,000 short backtick code spans -36.1% (5.3%)
1,000 inline links in a paragraph -31.0% (2.0%)
10,000 character entity references -29.9% (2.2%)
one CommonMark example -13.8% (5.5%)
CommonMark spec * 35 (~564 KB) -10.6% (3.6%)
CommonMark spec * 7 (~113 KB) -7.8% (18.3%)
full CommonMark spec (~16 KB) -7.4% (43.5%, NOISY)
Every input that drives a non-trivial number of events through the
dispatch loop benefits, because Object.assign was the cost being
eliminated. Inputs with high node-count-per-byte (many small inline
tokens) gain the most.
Trade-offs and inputs where the gain is small or absent:
A 10,000-unmatched-asterisks input and a 256 KB Unicode-heavy input
both moved within +/- 1% of baseline. Their event count per byte is
low, so the per-event Object.assign was not a hotspot.
The pure emphasis stress inputs ('a**b' repeated 10,000 times and
similar) reported large deltas in single runs but their cross-run
spread is 44 to 53% on the baseline alone. The input shape (mostly
attentionSequence events that mostly do not match a handler) means
the per-event merge was not the cost driver. The deltas there are
noise.
Tests pass: dev + prod 1448/1448, 100% coverage maintained,
mdast-util-gfm 54/54, mdast-util-mdx 11/13. The two failing mdx tests
reproduce on upstream/main and are not introduced by this branch.1 parent f9ef1b3 commit 5deffad
2 files changed
Lines changed: 34 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
247 | 247 | | |
248 | 248 | | |
249 | 249 | | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
250 | 258 | | |
251 | | - | |
252 | | - | |
253 | | - | |
254 | | - | |
255 | | - | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
260 | 276 | | |
261 | 277 | | |
262 | 278 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
6 | 15 | | |
7 | 16 | | |
8 | 17 | | |
| |||
0 commit comments