A profile of the benchmark worlds showed where a sync spends its time.
- The token sync of shapes that apply no tokens took 38% of a sync in
the 1000-copy world: for every synced shape, generate-update-tokens
walked every sync attribute, resolved its group and its token
attributes, and only then found that neither the main shape nor the
copy applies a token. It now returns at once, and the token
attributes of each sync attribute are computed once, at load.
- A sync looks through every object of each page and component to
find the copies it updates, so in a file with many shapes and few
copies the search costs more than the sync itself. The new benchmark
world main-edit-among-many-shapes syncs a main edit into 10 copies in
a page with 20,000 plain rects; its baseline, measured before the
change below, joins the recorded one with a note, and its result
joins the golden master. generate-sync-container called the
uses-assets? multimethod on every object through a lazy filter; the
predicate is now chosen once per container (asset-user?) and the
objects walked with reduce-kv into a vector, in the same order.
- compare-children searched the remaining children (d/seek) for every
child out of place, and dropped a child taken out of order with a
lazy remove, so a reorder took quadratic time and nested one lazy
sequence per child taken. It now looks children up by their
reference or id (index-positions) and skips those taken out of order
when the walk reaches them, with the same callbacks in the same
order. The sync of a reorder stays slow: when a main with 1,000
children moves its first one to the end, each copy still gets 999
moves (F85), and applying them is what costs (4.75 s to 4.26 s for
10 copies). Fewer moves change what a sync writes, so that waits for
Phase 4c.
No behavior change: the golden masters and the suites are unchanged.
Benchmark (lower quantile, ms; baseline / now / ratio):
deepest-edit-through-three-levels 35.2 / 18.6 / 0.53
edit-through-a-chain-of-dependents 68.9 / 36.4 / 0.53
library-update-into-copies 83.1 / 43.0 / 0.52
main-edit-among-many-shapes 4.8 / 2.3 / 0.48
main-edit-into-10-copies 1.8 / 0.9 / 0.48
main-edit-into-100-copies 15.4 / 8.4 / 0.54
main-edit-into-1000-copies 153.1 / 90.1 / 0.59
variant-switch-into-copies 32.1 / 20.0 / 0.63
(main-edit-among-many-shapes: baseline measured before the search
change.)
AI-assisted-by: claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The coming refactors and fixes change sync code, and a slowdown must be
seen and explained in the commit that causes it. A JVM benchmark now
times component sync on fixed-size worlds:
- one main into 10, 100 and 1000 copies;
- a change in the deepest of three nesting levels;
- a chain of dependents (a in b in c, with copies of each);
- a variant switch in a main with 100 copies;
- a library update into a file with 500 copies.
Each world is built once; only the sync an edit causes is timed, run
to a fixpoint as the frontend watcher does. Each checks its result, so
a world that synced nothing fails instead of timing nothing.
`scripts/bench-components record` writes the baseline, recorded here on
the code before this commit; `scripts/bench-components` compares against
it and exits 1 when a world is more than 10% slower. It compares the lower
quantile: with the devenv running, the mean of the same code moved
from 0.41 to 1.13 of the baseline, the lower quantile by under 12%.