Repository navigation
perf(studio): films with thousands of timed clips stop re-reading every clip each frame - #6805
Conversation
…ry clip each frame
There was a problem hiding this comment.
Verdict: REQUEST CHANGES · 1 blocker · 0 important · 1 nit · head 0407447f
- blocker —
Commentsis red on this head: the comment-share ratchet fails forpackages/core/src/runtime/init.ts(897→899 comment lines) andpackages/studio/src/player/hooks/timelineSyncHydration.ts(92→94). Cut the two new comment lines in each file, or fold the explanation into a name. ↓ inline
Scope
Read all 5 files and checked withSelectorIndexPass's rule: nothing in either pass writes to the DOM, and every getSourceScopedSelectorIndex call inside goes through getTimelineElementSourceFile. The per-revision start cache follows the same revision contract as the seek's other caches. Ran both named tests locally against the merge-base sources: they fail without the fix (expected 60 to be +0, expected 100 to be less than or equal to 1) and pass with it. Studio src/player/lib + timelineDomBinding pass: 307/307. CI and Windows were still running when I posted this.
| timelines?: Readonly<Record<string, TimelineLike>>, | ||
| ): TimelineElement[] { | ||
| // One occurrence index per selector for the whole scan; see buildTimelineElementsFromClips. | ||
| return withSelectorIndexPass(doc, () => scanTimelineFromDOM(doc, rootDuration, timelines)); |
There was a problem hiding this comment.
nit — the new test only drives buildTimelineElementsFromClips. If this wrap were removed, nothing would fail. A sibling test that counts .w queries through parseTimelineFromDOM on the same 200-span document would pin it.
Edit accuracy: accurate 271 (base branch 271), smooth 112 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (2)
|
What
A film with thousands of timed clips (word-by-word captions, many cuts) no longer freezes Studio when you press play: pressing play at 3000 clips used to block the page for about 10 s, and now plays at 30 fps.
Why
Two per-clip costs grew with the square of the clip count or ran on every frame:
styleClipsDueVisibleOnce) through a fresh resolver per call, so the resolver's caches never hit. At 3000 clips that is thousands of attribute and ancestor reads per frame.parseTimelineFromDOMandbuildTimelineElementsFromClips) asked for each clip's occurrence index among its siblings without an index pass, so each clip ran its own whole-documentquerySelectorAlland resolved the source file of every match. The pass that makes this linear already existed for the editor's layer walk; these two entry points never used it.Related work
#6773 removed the other half of this on the timeline (clip redraws). No open change touches these two paths.
How
init.ts: the start of each never-styled clip is cached per timing revision (the revision the seek already reads), and the cheap start comparison runs before any DOM read. The condition is unchanged.timelineDOM.tsandtimelineSyncHydration.ts: the scan and the manifest hydration run insidewithSelectorIndexPass, one occurrence index per selector for the whole walk.Before
Pressing play on a generated 3000-clip film: one frame in the 6 s window, the page blocked for 9.8 s.
After
The same film with this change: 256 frames in 6 s, worst frame 117 ms. The two red frames at the start are what is left (the first frames after play, 100 to 117 ms); the rest stay under the 16 ms to 50 ms lines.
Test plan
fails without the fix:
reads a far clip's start once while playing, not on every frameinpackages/core/src/runtime/init.startsOnce.test.ts: 60 reads (one per frame) without the change, 0 with it.queries the document once for a class many clips share, not once per clipinpackages/studio/src/player/hooks/timelineDomBinding.test.ts: 100 queries without the change, at most 1 with it.What I measured
Generated film of N timed word clips over 120 s, Studio in headless Chrome on a Linux box (page frame rate over 6 s of play, worst frame, sum of long-task time over the window), three runs each, before then after:
Long-task time fell from 154 to 749 percent of the window to 0 to 53 percent. The studio
src/playertests (2431) pass three times in a row;oxfmtandoxlintare clean on the changed files.What I did NOT execute
Desktop's own long-project walk; this is Studio alone.
A build of the inspector package on the test box (its copy step exits 1 after the bundle and the Studio files are written, so I could not rule out a stale inspector in the CLI the numbers came from).
The 3000-clip runs still vary widely (10.6 to 33.8 fps after): another per-frame cost remains and is not part of this change.
Unit tests added/updated
Manual testing performed
Documentation updated (if applicable)
Comments follow CONTRIBUTING.md "Comments"