perf: stylesheet-aware snapshot (PERF-5) + cache/bloom/bg reuse - #492
perf: stylesheet-aware snapshot (PERF-5) + cache/bloom/bg reuse#492thelabcorner wants to merge 1 commit into
Conversation
Single coherent perf pass on the snapshot hot path — 1.82x diverse / 5.9x huge vs upstream/main (neutral snapdom.toRaw()), 0px diff via window.__SNAPDOM_FULL_PROPS (allow 137 vs 370 props). - css: split getStyle caches (WeakMap) + epoch invalidation via bumpEpoch() -> _invalidateSplitCaches(), frozen emptyStyle (§6/§8) - styles: allow-list scans author sheets + @import + adopted + shadow, shorthands->longhands, inline-seed top-down, UA diff 19 tags, A2 clone-before-mutate, B2 SVG early-return, C3/C8 hoists (§4) - background: reuse snapshot via getCachedSnapshot (§6) - pseudo: bloom filter for ::before/::after/::first-letter, now covers @import + shadow same as allow-list (§8) - tests: 110 passed / 1 failed / 3 skipped (d489 2.82% fails on clean upstream stashed, isolation proof), guard __tests__/utils.css.splitcache Each intermediate was 110/1 green; squash for one big PR. Happy to split into 4 PRs on request. Base: main (26/26 recent merges -> main). Rebased on upstream/main, no scratch/.idea, no whitespace churn.
|
Hi Jackson, thanks a lot for this, and for the care that went into writing it up. The I took it to a separate checkout and tested it in a few scenarios beyond the test suite.
I also measured where the speed comes from, on a repetitive 1950 node DOM, median of 7 runs:
So the win is essentially all from those two mechanisms. The rest of the pass (hoisted The direction is right, and it is the same one v3 takes: v3 already ships stylesheet-aware Genuinely, thank you. I would really like to have you back once v3 is out: the hot path |
perf: stylesheet-aware snapshot + cache/bloom reuse
Hi Juan, thanks for Snapdom and for all the work on v3! Wanted to share a perf pass I’ve been testing on
2.24.xin case it’s useful. I saw v3 is adding stylesheet-aware scanning and auto memoization, so happy to retarget this there if you prefer.Why this PR
snapdom.toRaw()spends most of its snapshot phase reading~370computed props per node withgetPropertyValue. Most of those are just browser defaults. On same-processsnapdom.toRaw()vsupstream/main(2dc5348):3563msto599ms(5.9x)332msto182ms(1.82x,BENCH-DIVERSE2.04x)window.__SNAPDOM_FULL_PROPS=1vs allow-list: 0px diffWhat changed
All on one hot path (
src/utils/css.jsandsrc/modules/styles.js/background.js/pseudo.js):document.styleSheetsplus@import,adoptedStyleSheetsand shadow roots once per epoch, expands shorthands to longhands and seeds with inline styles top-down.snapshotComputedStyleFullthen iterates 137 props instead of 370.MODULE_REQUIRED_PROPSplus a UA diff for 19 tags keepspre,th,tableand others correct.window.__SNAPDOM_FULL_PROPSforces a full read for verification.getStylenow uses splitWeakMapcaches fornulland pseudo, bridged tocache.computedStyleand cleared onbumpEpochso it does not return stale values after a mutation.emptyStyleis frozen.background.jsreuses the snapshot withgetCachedSnapshotinstead of 24k extra reads.pseudo.jscollects selectors for::before,::afterand::first-letterincluding@importand shadow, and skipsgetComputedStylewhenmatchessays there is nothing to do.No new
options, noscratchor.idea, based onmainand rebased on currentupstream/main. The whitespace diff that shows up is just the functionalif (!allow)guard (52 lines).Tests
npx vitest run --browser.headlessgives 110 passed, 1 failed, 3 skipped. The one failure isd489-reconcile-transformat 2.82 percent, and it also fails on a cleanupstream/mainstash. Added a small guard test__tests__/utils.css.splitcache.test.jsfor the epoch fix.Note on AI
I used AI (Muse Spark via OpenCode) to help iterate, measure and draft. All benchmarks are same-process medians with ranges checked, and the 0px check is in the repo so you can rerun it.
Really appreciate your work on this project. Happy to split this into separate PRs if you would like, just let me know. Thanks!