Skip to content

perf(site): use lazy iteration in sliceAtGraphemeBoundary#23671

Merged
mafredri merged 1 commit intomainfrom
mafredri/perf-smooth-text-lazy-iteration
Mar 26, 2026
Merged

perf(site): use lazy iteration in sliceAtGraphemeBoundary#23671
mafredri merged 1 commit intomainfrom
mafredri/perf-smooth-text-lazy-iteration

Conversation

@mafredri
Copy link
Copy Markdown
Member

Summary

Fix O(full-text) per-frame cost in the streaming text smooth-reveal animation by replacing eager array materialization with lazy iteration.

What changed

sliceAtGraphemeBoundary in SmoothText.ts used Array.from(graphemeSegmenter.segment(text)) to materialize the entire grapheme segment iterable into an array before iterating with an early break. The fallback codepoint path had the same issue with Array.from(text).

During streaming, this function runs on every animation frame (~60fps) for the actively streaming response block. The visible prefix is typically much shorter than the accumulated full text, so the early break should make the function O(prefix), but the eager Array.from forces O(full-text) regardless.

Replaced both paths with direct for...of iteration, which is lazy and exits at the break.

Benchmark results

Scenario Before (per call) After (per call) Speedup Frame budget saved
500 chars, prefix 50 0.145ms 0.016ms 8.3x 0.78%
2000 chars, prefix 100 0.560ms 0.031ms 17.9x 3.18%
5000 chars, prefix 200 1.442ms 0.063ms 22.6x 8.26%
10000 chars, prefix 200 2.949ms 0.064ms 43.1x 17.28%

Frame budget is measured against 16.7ms (60fps). At 10K characters (a medium-length response), the old code consumed 17.66% of the frame budget on grapheme slicing alone.

Verification

  • All 8 existing SmoothText.test.ts tests pass.
  • lint:compiler reports 181 functions compiled, 0 diagnostics.
  • useSmoothStreamingText hook still compiles cleanly.

Array.from(graphemeSegmenter.segment(text)) materializes the
entire text into an array before iterating, even though the loop
breaks early at the visible prefix length. During streaming at
60fps, this makes each frame O(full text) instead of O(prefix).

Benchmark on 5000-char text with 200-char prefix: 22.6x faster
(1.44ms to 0.06ms per call, saving 8.3% of the frame budget).
The fallback codepoint path had the same issue with Array.from.
@mafredri mafredri marked this pull request as ready for review March 26, 2026 14:33
@mafredri mafredri merged commit b23c07c into main Mar 26, 2026
34 checks passed
@mafredri mafredri deleted the mafredri/perf-smooth-text-lazy-iteration branch March 26, 2026 14:33
@github-actions github-actions bot locked and limited conversation to collaborators Mar 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants