Summary
On sheets with a modest dataset (~40 rows × 20 cols, padded grid 240×30), continuous wheel scrolling and arrow-key selection moves spend most of their budget re-rasterizing the canvas. The cost scales almost linearly with devicePixelRatio, and profiling shows text/style raster as the hot path — suggesting the viewport is re-drawn wholesale per frame / per selection move. There is currently no public knob to trade render resolution for performance (IUniverEngineRenderConfig is empty in 0.25.0).
Environment
@univerjs/presets 0.25.0 (also checked 0.25.1 — release notes say Pro-only fixes), presets: sheets-core, filter, sort, find-replace, conditional-formatting, data-validation, hyper-link, note
- Chrome 148, macOS 12, Intel MacBook, retina display (
devicePixelRatio 2)
- Viewports tested: 368×130 CSS px (docked panel) and 1404×547 CSS px (full screen)
Measurements
Synthetic wheel storm (~60 events/s for 8 s) and arrow-key walk (36 presses at ~8 Hz), measured with rAF sampling, the Event Timing API, and CDP Performance.getMetrics deltas:
| Scenario |
Main-thread scripting / 8 s scroll |
Arrow-press p95 (Event Timing) |
| 368×130 viewport, DPR 2 |
~2,440 ms |
~104 ms (20/36 presses ≥16 ms) |
| 1404×547 viewport, DPR 2 |
~2,830 ms |
~112 ms (28/36 ≥16 ms) |
same, devicePixelRatio overridden to 1.5 before boot |
−53…57% (≈1,150–1,220 ms) |
~56 ms |
Attribution:
- DevTools trace (8 s docked scroll): ~2.57 s of 2.95 s scripting self-time inside the Univer bundle, dominated by
FunctionCall under the wheel/rAF pipeline.
- 5 kHz CPU profile (unminified build, 6.4 s scroll storm) — top self-time frames after idle/program:
fillText, canvas font getter, hexToColor / toHexString, drawImage, getBoundingClientRect. Filter/sort code contributes ~0 ms (their presence or absence doesn't change the profile).
Happy to attach the full .cpuprofile and trace JSON.
Requests
- Expose a render
pixelRatio / render-scale option (engine-render reads window.devicePixelRatio internally in several places — getDevicePixelRatio() at init and direct reads on resize/buffer paths — so hosts today can only override the global getter, which is a blunt instrument). A per-instance cap would let hosts trade a little sharpness for a ~2× main-thread win on weak-GPU / hi-DPI machines.
- Consider caching/partial redraw on the scroll and selection-move paths. The DPR sensitivity plus the
fillText/font/color-conversion dominance suggests visible cells are re-rasterized wholesale on every frame and every selection move; a row/cell raster cache or dirty-region redraw would likely help all hosts without any API change.
Thanks for Univer — the 0.25.0 scroll improvements are noticeable; this is the next bottleneck we hit in production profiling.
Summary
On sheets with a modest dataset (~40 rows × 20 cols, padded grid 240×30), continuous wheel scrolling and arrow-key selection moves spend most of their budget re-rasterizing the canvas. The cost scales almost linearly with
devicePixelRatio, and profiling shows text/style raster as the hot path — suggesting the viewport is re-drawn wholesale per frame / per selection move. There is currently no public knob to trade render resolution for performance (IUniverEngineRenderConfigis empty in 0.25.0).Environment
@univerjs/presets0.25.0 (also checked 0.25.1 — release notes say Pro-only fixes), presets: sheets-core, filter, sort, find-replace, conditional-formatting, data-validation, hyper-link, notedevicePixelRatio2)Measurements
Synthetic wheel storm (~60 events/s for 8 s) and arrow-key walk (36 presses at ~8 Hz), measured with rAF sampling, the Event Timing API, and CDP
Performance.getMetricsdeltas:devicePixelRatiooverridden to 1.5 before bootAttribution:
FunctionCallunder the wheel/rAF pipeline.fillText, canvasfontgetter,hexToColor/toHexString,drawImage,getBoundingClientRect. Filter/sort code contributes ~0 ms (their presence or absence doesn't change the profile).Happy to attach the full
.cpuprofileand trace JSON.Requests
pixelRatio/ render-scale option (engine-render readswindow.devicePixelRatiointernally in several places —getDevicePixelRatio()at init and direct reads on resize/buffer paths — so hosts today can only override the global getter, which is a blunt instrument). A per-instance cap would let hosts trade a little sharpness for a ~2× main-thread win on weak-GPU / hi-DPI machines.fillText/font/color-conversion dominance suggests visible cells are re-rasterized wholesale on every frame and every selection move; a row/cell raster cache or dirty-region redraw would likely help all hosts without any API change.Thanks for Univer — the 0.25.0 scroll improvements are noticeable; this is the next bottleneck we hit in production profiling.