Skip to content

v1.0.3

Choose a tag to compare

@github-actions github-actions released this 01 Jun 14:39
· 7 commits to main since this release
f4017ad

A performance release. No API changes; results are identical to 1.0.2.

Performance

Profile-driven tuning of the core step evaluator (evaluateStep). Earlier
guesses aimed at cutting object/GC allocation, but on this engine GC is only
~3.5% of runtime — so a CPU profile (node --prof bench/bench.js) was used to
find the real hot spots:

  • Megamorphic axis lookup — the axis function is now resolved once per
    step
    (resolveAxis) instead of once per input node, eliminating a
    megamorphic AXES[axis] keyed-load IC (~6.5% → ~1.6% of ticks).
  • Redundant predicate classification — a predicate's "is it a pure node-set
    (existence filter) vs a position test" is a static property of the AST, now
    computed once per step instead of once per input node (isPureNodeSet
    ~3.3% → ~0.1%); predicate-less steps skip the predicate pass entirely.
  • Defensive array copies — the child and attribute axes no longer
    .slice() the adapter's arrays. evaluateStep treats axis results as
    read-only (it copies matches into a fresh array), so an axis array is never
    mutated or used as a node-set's backing store. This removes ~5% of slice
    (CloneFastJSArray) overhead; a regression test guards the invariant.
  • Fused descendant walk — a selective //name applies its node test during
    the descendant traversal instead of materializing the whole descendant set
    and then filtering it.
  • Empty input node-sets short-circuit before any per-step setup.

On the capybara-simulated–shaped benchmark this is ~10–29% faster across the
query mix, with scaling still linear:

query 1.0.2 1.0.3
count all elements 0.160ms 0.114ms
all fields (union-in-predicate) 0.396ms 0.306ms
field by label (//label hoist) 0.743ms 0.597ms
deep descendant attr 0.261ms 0.215ms
link by text 0.281ms 0.231ms
table cell by text 0.570ms 0.512ms

Full Changelog: v1.0.2...v1.0.3