Commit 42ca832
committed
perf(compile): reuse dispatch handler context
The compile loop calls Object.assign({sliceSerialize: ...}, context)
once per event so each handler's this-binding has a fresh
sliceSerialize plus all the shared context fields. For a typical
document this is one allocation per event in the hot loop. For larger
inputs (~140k events at the 564 KB scale) that is tens of thousands
of short-lived merged objects per parse, plus the per-allocation
hidden-class transitions.
The fix builds the merged this-binding once outside the loop and
mutates only its sliceSerialize field per event. Stack, tokenStack,
data, config, and the enter/exit/buffer/resume helpers are still the
same references that context exposes, so any handler reading or
mutating those still sees the right object. Existing handlers do not
capture this across events, so a single shared callContext is
observably equivalent to the old per-event merge.
Inputs that benefit, with multi-run median-of-medians vs the baseline
(spread in parentheses):
10,000 character entity references -43.7% (6.5%)
1,000 inline links in a paragraph -37.1% (6.4%)
10,000 short backtick code spans -32.6% (4.1%)
CommonMark spec * 35 (~564 KB) -13.4% (3.3%)
CommonMark spec * 7 (~113 KB) -10.1% (7.1%)
one CommonMark example -9.1% (8.8%)
full CommonMark spec (~16 KB) -5.4% (9.2%)
Every input that drives a non-trivial number of events through the
dispatch loop benefits, because the per-event Object.assign is the
cost being eliminated. Inputs with high node-count-per-byte (many
small inline tokens) gain the most. Single-run full corpus shows
wins between -16% and -39% on a 1,000-fenced-code-block input, a
5,000-ATX-heading input, a 5,000-HTML-block input, a 1,000-image
input, a 5,000-tab-heavy input, and a 1,000-autolink input.
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. A 1 MB single
paragraph and a 5,000-reference-link-definition input each shifted a
few percent on a single run; both sit inside their own noise bands.
The pure emphasis stress inputs ('a**b' repeated 10,000 times and
similar) reported +30% to +44% but their cross-run spread is 47 to
53% on this stack alone, and the input shape (mostly attentionSequence
events that mostly do not match a handler) means the per-event merge
was not the cost driver. The regression there is noise.
Tests pass: dev + prod 1448/1448, 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 42ca832
1 file changed
Lines changed: 18 additions & 10 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 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
250 | 262 | | |
251 | | - | |
252 | | - | |
253 | | - | |
254 | | - | |
255 | | - | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | | - | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
261 | 269 | | |
262 | 270 | | |
263 | 271 | | |
| |||
0 commit comments