Skip to content

perf(gpui): speed up 100KB script input and scrolling (105–348× in warm CPU benchmarks) - #3208

Merged
richiemcilroy merged 2 commits into
CapSoftware:mainfrom
kartikkabadi:perf/gpui-long-script-input
Oct 25, 2026
Merged

richiemcilroy merged 2 commits into
CapSoftware:mainfrom
kartikkabadi:perf/gpui-long-script-input

Conversation

@kartikkabadi

@kartikkabadi kartikkabadi commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Moving the caret or scrolling a long teleprompter script repeatedly scans the full string, rebuilds the interaction layout during flex measurement, and enters glyph loops for offscreen paragraphs. This keeps the warm interaction path short while preserving existing text geometry and editing behavior.

 warm text-field interaction
- scan graphemes from a string endpoint
+ find the adjacent boundary from the caret with full Unicode context
- replace the active row layout during width measurement
+ retain the active layout and up to four alternate width measurements
- enter every paragraph's glyph paint loop
+ skip fully clipped unmarked paragraphs

One file, +487 / -72, including five focused regressions. Production code accounts for +192 / -70; tests account for +295 / -2. The cache tracks the complete inherited font, color, text, composition range, font size, line height and active wrap width. Deferred measurement captures the inherited font. Marked text keeps its existing painting path, and paragraph clipping preserves GPUI's fractional row coordinates.

Evidence

Fresh timing comparison against main ec15b9840344 on Apple M1 Pro, macOS 27.0.1, Rust 1.95.0. The exact committed suite was subsequently rebuilt at 900dd827b962. Last-checked main 100f43a0d359 added web/CLI-only changes; its GPUI workspace, Rust crates and build configuration are identical to the tested tip. Both measured text-input sources remain unchanged throughout. The title's rounded range covers the ~100KB key-dispatch/CPU-draw and scroll-redraw cases across ASCII, multilingual and emoji scripts.

For the 100KB ASCII fixture with the viewport near the middle:

Operation Before per step After per step Median paired speedup Across the three pairs
Left key + CPU redraw 11,453.125 µs 48.454 µs 237.73× 233.59–285.29×
Right key + CPU redraw 12,101.652 µs 53.878 µs 224.90× 213.59–272.20×
Shift-left key + CPU redraw 11,669.375 µs 61.855 µs 188.66× 179.98–257.02×
Shift-right key + CPU redraw 12,029.483 µs 61.584 µs 195.81× 191.12–993.73×
Scroll redraw 10,298.966 µs 44.803 µs 230.81× 229.87–359.78×

Ranges of case-level median paired speedups across the full text/position matrix:

Target script size Key dispatch + CPU redraw Scroll CPU redraw
10KB 9.97–21.47× 11.76–23.68×
100KB 105.33–237.73× 142.89–347.73×

These measure elapsed time for warm, synchronous CPU operations in GPUI TestPlatform/NoopTextSystem. Key measurements include binding dispatch and CPU scene preparation; scroll measurements include offset changes and CPU redraw. Native font shaping, OS input latency, GPU presentation, FPS, release-profile performance and export throughput remain unmeasured. The native checks below validate rendering correctness separately.

Immutable evidence snapshot includes all raw samples, fixture sources, compiler profiles, build/test logs, native images, analysis scripts, source hashes and a per-file checksum manifest. The two earlier audit archives are preserved there with their original manifests. Evidence files are outside this PR's code diff.

Benchmark method and reproduction
  • Three alternating process pairs, seven samples per case and 120 operations per sample. All 2,772 raw samples / 66 cases independently reproduce the reported medians and ratios. Each process must produce the same fixture/viewport/caret case keys.
  • ASCII, multilingual and emoji scripts targeting 10,000 and 100,000 bytes. Actual byte/grapheme counts are in the CSV. Navigation covers middle positions with left/right and Shift-left/Shift-right handlers and binding dispatch; scrolling covers start/middle/end positions.
  • Window 560×320, scroll viewport 560×240, text width 496, font size 30, line height 45, centered text. A two-row scroll round trip and the navigation viewport are prepared outside the timed loop.
  • Each process contributes the median of seven samples. Speedup is the median of the three paired before/after ratios. The displayed before/after times are medians of process medians; their quotient need not equal the median paired ratio. All per-pair ranges are retained.
  • App opt-level 3, debug info disabled, debug assertions and overflow checks enabled. This is an optimized test build, with dependencies using the repository's development settings. All 1,018 compiler-artifact feature/profile records match between the two builds. The exact committed suite also passed at the app's normal opt-level 0.
  • Shared 8-logical-CPU, 16GB host, without CPU-affinity or power-state control. No build or other audit test ran concurrently with these final measurements. Other host workloads were uncontrolled.
  • Earlier minimization checks include a five-pair identical-binary control. That control showed apparent differences approaching 1.9× despite identical executables. The original-versus-minimized repeat showed no consistent regression attributable to minimization. Those records are retained; the headline numbers above come from the fresh current-main comparison.

Raw summary CSV, process metadata, independent analysis, compiler profile, source/patch integrity and reproduction notes.

After the repository's normal native setup, build two disposable checkouts at the recorded baseline and this PR's code commit. The evidence includes exact pure source snapshots and the external audit fixtures. Append the viewport fixture under cfg(test) to each source, and use:

CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo +1.95.0 test \
  --manifest-path apps/desktop-gpui/Cargo.toml --locked --offline \
  --bin cap-gpui --no-run \
  --config profile.dev.package.cap-desktop-gpui.opt-level=3 \
  --config profile.dev.package.cap-desktop-gpui.debug=0 \
  --config 'profile.dev.package."*".debug=0' --message-format=json

python3 run-viewport-pairs.py --repo <checkout> \
  --baseline <baseline-test-binary> --candidate <candidate-test-binary> \
  --build-profile <candidate-profile.json> --pairs 3 --matrix core --output <results>

The retained build scripts show the exact commands and native runtime environment used. Adjust their recorded workspace paths for reproduction. Source snapshots and executable hashes identify the audited builds. The native example is built separately with gpui_platform/test-support and captures both builds at the same display scale. Executables, app profiles and dependency caches are excluded from the evidence snapshot.

All 66 benchmark cases

Times are µs per step. Handler-only rows exclude drawing and are kept separate from the title's key/scroll CPU range.

Text Size Position Motion Scope Before µs After µs Median paired Pair range
ascii 10KB middle left Key + CPU draw 1,062.0014 47.9951 21.47× 16.23–23.50×
ascii 10KB middle left Handler only 61.2753 0.0767 802.12× 783.83–819.49×
ascii 10KB middle right Key + CPU draw 1,089.2920 52.0896 21.41× 13.92–21.50×
ascii 10KB middle right Handler only 112.4538 0.0851 1321.94× 1308.39–1365.07×
ascii 10KB middle select_left Key + CPU draw 1,076.0861 57.1007 17.91× 16.31–19.62×
ascii 10KB middle select_left Handler only 61.1260 0.0781 784.37× 782.41–981.72×
ascii 10KB middle select_right Key + CPU draw 1,132.7073 56.5052 19.45× 14.96–20.05×
ascii 10KB middle select_right Handler only 113.1562 0.0844 1348.67× 1341.11–1442.46×
ascii 10KB start none Scroll CPU draw 962.8306 41.2184 23.29× 21.13–23.39×
ascii 10KB middle none Scroll CPU draw 959.6101 42.4420 22.61× 20.61–23.00×
ascii 10KB end none Scroll CPU draw 965.6875 41.0566 23.68× 22.68–23.76×
multilingual 10KB middle left Key + CPU draw 599.8476 51.9722 11.59× 11.13–12.06×
multilingual 10KB middle left Handler only 51.6920 0.0976 537.43× 525.64–549.59×
multilingual 10KB middle right Key + CPU draw 646.7691 56.5438 11.36× 11.14–12.05×
multilingual 10KB middle right Handler only 82.8677 0.1153 717.87× 701.92–779.59×
multilingual 10KB middle select_left Key + CPU draw 600.3396 60.1938 9.97× 8.98–10.10×
multilingual 10KB middle select_left Handler only 51.7351 0.0990 524.61× 482.98–542.61×
multilingual 10KB middle select_right Key + CPU draw 618.3878 60.2125 10.09× 8.90–11.99×
multilingual 10KB middle select_right Handler only 81.1778 0.1156 687.61× 686.02–769.16×
multilingual 10KB start none Scroll CPU draw 532.0486 41.3871 12.86× 12.19–14.04×
multilingual 10KB middle none Scroll CPU draw 546.7656 46.6632 11.76× 10.58–12.92×
multilingual 10KB end none Scroll CPU draw 534.9163 41.9601 12.75× 12.41–13.10×
emoji 10KB middle left Key + CPU draw 969.8663 47.1774 20.56× 20.14–20.79×
emoji 10KB middle left Handler only 57.2267 0.0781 737.40× 690.51–739.05×
emoji 10KB middle right Key + CPU draw 1,000.6222 51.9559 19.25× 19.23–20.79×
emoji 10KB middle right Handler only 101.8740 0.0882 1159.62× 1051.60–1251.28×
emoji 10KB middle select_left Key + CPU draw 970.4059 56.0108 17.23× 16.85–18.40×
emoji 10KB middle select_left Handler only 57.4476 0.0819 701.08× 698.80–708.18×
emoji 10KB middle select_right Key + CPU draw 1,025.8646 56.1701 18.33× 18.08–18.42×
emoji 10KB middle select_right Handler only 102.7424 0.0906 1134.34× 1124.02–1164.99×
emoji 10KB start none Scroll CPU draw 911.9719 41.2413 22.26× 21.69–23.17×
emoji 10KB middle none Scroll CPU draw 913.6128 42.8073 21.37× 20.98–22.36×
emoji 10KB end none Scroll CPU draw 900.8705 41.6746 21.62× 21.29–21.84×
ascii 100KB middle left Key + CPU draw 11,453.1253 48.4542 237.73× 233.59–285.29×
ascii 100KB middle left Handler only 613.8167 0.0757 8146.21× 7964.26–8180.65×
ascii 100KB middle right Key + CPU draw 12,101.6524 53.8781 224.90× 213.59–272.20×
ascii 100KB middle right Handler only 1,195.4670 0.0840 14226.16× 13234.19–16610.83×
ascii 100KB middle select_left Key + CPU draw 11,669.3750 61.8545 188.66× 179.98–257.02×
ascii 100KB middle select_left Handler only 610.4674 0.0792 7848.34× 7570.21–10733.40×
ascii 100KB middle select_right Key + CPU draw 12,029.4830 61.5837 195.81× 191.12–993.73×
ascii 100KB middle select_right Handler only 1,145.2601 0.0844 13687.00× 13122.90–58962.18×
ascii 100KB start none Scroll CPU draw 14,640.8660 42.1042 347.73× 281.29–391.76×
ascii 100KB middle none Scroll CPU draw 10,298.9656 44.8035 230.81× 229.87–359.78×
ascii 100KB end none Scroll CPU draw 13,395.3389 44.2097 290.46× 235.76–303.00×
multilingual 100KB middle left Key + CPU draw 6,250.3764 50.2705 124.33× 122.93–208.92×
multilingual 100KB middle left Handler only 544.6726 0.1215 4537.90× 4481.98–6467.08×
multilingual 100KB middle right Key + CPU draw 6,272.0035 53.3767 118.17× 115.54–188.74×
multilingual 100KB middle right Handler only 776.5788 0.0868 9000.57× 8945.94–12724.62×
multilingual 100KB middle select_left Key + CPU draw 6,354.5337 60.3924 105.33× 98.34–150.64×
multilingual 100KB middle select_left Handler only 559.3913 0.1208 4760.43× 4629.46–5928.14×
multilingual 100KB middle select_right Key + CPU draw 6,410.3969 60.2306 106.43× 103.44–125.82×
multilingual 100KB middle select_right Handler only 804.1306 0.0851 9491.07× 8987.61–10794.09×
multilingual 100KB start none Scroll CPU draw 6,224.7684 42.1677 142.89× 131.81–150.60×
multilingual 100KB middle none Scroll CPU draw 6,711.8236 44.4753 151.86× 119.31–182.35×
multilingual 100KB end none Scroll CPU draw 6,102.3285 42.7024 144.22× 129.37–144.41×
emoji 100KB middle left Key + CPU draw 10,563.7198 49.9507 205.61× 200.34–332.77×
emoji 100KB middle left Handler only 594.1542 0.0823 6924.40× 6900.10–7706.05×
emoji 100KB middle right Key + CPU draw 11,431.4035 53.2903 193.63× 190.71–283.96×
emoji 100KB middle right Handler only 1,122.8167 0.0868 12935.68× 11401.02–13860.51×
emoji 100KB middle select_left Key + CPU draw 11,057.9194 61.5580 177.74× 162.25–189.11×
emoji 100KB middle select_left Handler only 592.4993 0.0840 7352.45× 6774.57–7387.03×
emoji 100KB middle select_right Key + CPU draw 11,428.2972 60.9476 188.38× 169.03–213.14×
emoji 100KB middle select_right Handler only 1,047.4684 0.0910 11513.80× 11016.30–12313.91×
emoji 100KB start none Scroll CPU draw 9,728.7066 42.5514 225.93× 220.94–255.19×
emoji 100KB middle none Scroll CPU draw 11,396.9778 44.3861 246.87× 216.41–281.18×
emoji 100KB end none Scroll CPU draw 9,670.4868 43.1924 221.82× 214.29–232.55×
Correctness, regression tests and final source review
Check Result
Exact one-file committed source, normal app opt-level 0 1,430 passed, 0 failed, 24 pre-existing opt-in tests ignored
Extended audit, app opt-level 3 1,439 passed, 0 failed, 26 ignored; cache diagnostic and viewport benchmark also executed explicitly
Existing/focused component tests in the PR All 25 existing tests retained, plus five focused regressions
Geometry, selection and hit testing 480 exact baseline/candidate matches, including cold/warm layout, width changes, empty/Unicode text, alignments, single/multiline and fit-content modes
Native font/Metal rendering 108 byte-identical PNG/geometry pairs across ASCII, Unicode, empty lines and one long paragraph, fractional line heights, scroll positions, selection and composition
Native font/color invalidation Four cached-versus-fresh font/color transitions pass in each build
Unicode 374,742 offsets in both directions; official Unicode 17 expectations cover 766 cases / 7,801 offsets
Layout-cache regression Baseline probe fails on right, Shift-right and scroll; candidate retains the layout for all three
Scoped binary/test Clippy Same 49 pre-existing app diagnostics, zero new, none in the changed file
Formatting Changed-file rustfmt and patch whitespace pass; full-app rustfmt reports four untouched upstream files, verified byte-identical to main
Patch integrity Independently applies to a temporary index at checked main and produces the exact final source; no audit hooks or temporary native example in the PR

The five repository regressions cover Unicode navigation at every byte offset in explicit fixtures, fractional wrapped-row clipping, width-probe/cache retention, resize/text/style/composition invalidation, and inherited fonts across deferred layout. The seven original exhaustive audit regressions and two independent geometry/composition checks were preserved and rerun outside the code diff.

Unicode verification uses direct old/new evaluation for short inputs and sampled long offsets, with the old forward/reverse iterator boundary oracle for all long offsets. Official Unicode expected chunks provide an independent conformance check. Both production helper bodies exactly match the freshly executed verification sources.

The native fixture uses the real production component, platform fonts and Metal with a fixed test theme. Its current baseline and candidate captures are both 560×320 at the same backing scale. Earlier display scales are retained in separate historical snapshots. Composition/editing/undo/redo checks pass, but these are not real system IME event entry or full signed-in app acceptance.

The other 24 existing ignored tests are opt-in audio/device/GPU/fixture benchmarks and real-app recording/upload integrations. They remain unexecuted; the logs list every ignored test and any recorded reason. No check was weakened or production failure waived.

Committed test output, extended test output, cache diagnostic, validation record, native captures, Unicode verification, Clippy output, formatting baseline comparison and final source review.

The final review traced settings/search, renaming, editor fields, screenshot/annotation inputs and teleprompter editing/scrolling. It checked cache ownership and invalidation, Unicode context, measurement geometry, clipping/decorations, the deferred font lifetime and shared public contracts. No unresolved correctness finding remained.

Latest main and duplicate PR check

Rechecked upstream main and the changed-file lists of all 154 open PRs, plus 100 recently merged PRs and historical text-input/grapheme/teleprompter searches. No published duplicate found in that audit.

The only open PR touching this file is #2587; its patch changes theme styling and removes selection_tint. It does not implement grapheme cursor navigation, measurement retention or paragraph culling. Recently merged #3125 only adds the focus accessor in this file, which this patch preserves. #2773 handles platform editing shortcuts, #2524/#2739 concern renderer text-overlay caches, and #3104 concerns platform frame scheduling.

Compared with the original audit base, current main's only change in this file is is_focused. It is preserved. The exact suite was rebuilt and rerun after the unrelated Windows-picker change at 900dd827b962. The PR branch starts at last-checked main 100f43a0d359; its later web/CLI-only commits leave all GPUI workspace, Rust crate and build-configuration trees identical to that tested tip. Both measured text-input sources remain byte-identical. Source-tree identity record, duplicate audit record.

Merge danger

Door: two-way. Reverting this one-file change restores the previous implementation. There are no dependency, persistence, migration, recording or export format changes.

Blast radius: shared text fields. Native Windows/Linux execution, system IME event entry and full signed-in app acceptance were not performed locally. Cold shaping, text edits, style changes and resizing can still scale with document length. Some Unicode boundaries require scanning surrounding context, and marked composition deliberately bypasses paragraph culling. This is a scoped improvement to warmed input and CPU redraw work.

RetriggerView in GreptileConfidence Score: 5/5

The reviewed changes appear safe to merge; no actionable issue was found.

Summary

Speeds up shared GPUI text fields by finding nearby grapheme boundaries, keeping the active layout during width measurements, and skipping fully clipped paragraphs.

  • Text fields keep their active layout while flex checks different widths.
  • Caret movement finds nearby Unicode character boundaries from the current position.
  • Scrolling text fields skip painting paragraphs outside the visible area.

Reviews (1) · Last reviewed commit: "perf(gpui): reuse text layouts during lo..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

@kartikkabadi is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

@richiemcilroy
richiemcilroy merged commit 361b8cc into CapSoftware:main Oct 25, 2026
51 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants