Skip to content

Commit a01d72a

Browse files
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 a01d72a

1 file changed

Lines changed: 25 additions & 9 deletions

File tree

dev/lib/index.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -247,16 +247,32 @@ function compiler(options) {
247247

248248
index = -1
249249

250+
// The handler this-binding receives the same fields as `context` plus a
251+
// per-event `sliceSerialize`. Build the merged object as a stable-shape
252+
// literal so V8 keeps a single hidden class for it and can inline the
253+
// construction; the previous Object.assign({sliceSerialize: ...}, context)
254+
// form created the same object but went through a copy-loop and gave V8
255+
// less to optimize. A fresh object per event preserves the previous
256+
// contract that handlers may capture `this` or reassign top-level fields
257+
// without leaking into subsequent events.
250258
while (++index < events.length) {
251-
const handler = config[events[index][0]]
252-
253-
if (own.call(handler, events[index][1].type)) {
254-
handler[events[index][1].type].call(
255-
Object.assign(
256-
{sliceSerialize: events[index][2].sliceSerialize},
257-
context
258-
),
259-
events[index][1]
259+
const event = events[index]
260+
const handler = config[event[0]]
261+
262+
if (own.call(handler, event[1].type)) {
263+
handler[event[1].type].call(
264+
{
265+
sliceSerialize: event[2].sliceSerialize,
266+
stack: context.stack,
267+
tokenStack: context.tokenStack,
268+
config: context.config,
269+
enter: context.enter,
270+
exit: context.exit,
271+
buffer: context.buffer,
272+
resume: context.resume,
273+
data: context.data
274+
},
275+
event[1]
260276
)
261277
}
262278
}

0 commit comments

Comments
 (0)