Skip to content

perf(grid): rebuild only changed rows and slim down _GridCell to fit the 120 Hz frame budget #972

Description

@ZhuchkaTriplesix

Summary

Profile-mode frame timings show VirtualResultGrid still does not fit the 120 Hz budget (8.33 ms) on a large, wide result, even after #875 (no re-sort on edit) and #879 (no setState per scroll tick). The remaining cost is widget build, not sorting or raster: every rebuild recreates all visible _DataRow / _GridCell widgets, including rows whose data did not change.

Measurements

Setup: flutter run --profile -d linux, 120 Hz display, 5000 rows × 120 columns, frame timings from SchedulerBinding.addTimingsCallback, 6 s per run. Baseline = dev before #875/#879. Synthetic stress: pan 14 px per frame, or one staged cell edit per frame on a grid sorted by column 0.

Scenario build p50 frames over 8.33 ms frames in 6 s
Horizontal pan, before 23.7 ms 112 / 113 113
Horizontal pan, after #879 0.65 ms 50 / 157 157
Edit on sorted grid, before 28.3 ms 113 / 114 114
Edit on sorted grid, after #875 17.9 ms 178 / 179 179

Raster is stable at about 4 ms (p50), i.e. half the budget on its own.

  • Pan: most frames are cheap now, but about a third (the ones where the visible column window changes) take about 50 ms to build.
  • Edit: a single cell edit still costs about 18 ms of build because all visible rows are rebuilt.

The benchmark app used for this is benchmark/grid_perf_bench.dart (MODE=scroll / MODE=edit); it is not in the repo yet.

Hypothesis (not yet verified)

  • _DataRow widgets are new instances on every build, so Element.updateChild cannot skip them, and each visible row rebuilds all of its _GridCells (hundreds to thousands of cells on a large window).
  • _GridCell is heavy per instance (gesture / tooltip / decoration layers), so even a cheap change multiplies.

Needed first: function-level profile

The numbers above only say build is slow. A function-level CPU profile (DevTools CPU profiler / Timeline in profile mode, on a full-screen window) is still required to confirm where the time goes before choosing a fix. Note the window size matters a lot: more visible cells means more build work, so the profile must be taken with a full-size (maximized) window.

Scope

  • Capture the function-level profile for both scenarios and attach the top self-time frames.
  • Stop rebuilding rows whose displayed data and status did not change (for example an == on _DataRow keyed on a per-row version from the staging buffer, so Element.updateChild can skip it).
  • Reduce per-cell cost in _GridCell (fewer wrapper widgets / layers per cell).
  • Re-run the benchmark and record before / after numbers in the PR.
  • Optionally add benchmark/grid_perf_bench.dart (or a trimmed version) so the measurement is repeatable.

Acceptance

  • Edit on a sorted 5000 × 120 grid: build p90 under 8.33 ms in profile mode on a full-screen window.
  • Horizontal pan: frames where the column window changes stay under 8.33 ms build.
  • No change in selection, editing, staged-status colors or virtualization behaviour (existing grid tests stay green).

Out of scope

  • Column auto-fit sampling in LayoutBuilder.
  • Changing the staging buffer's copy of original rows.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

data-gridInteractive data grid, cell editor, filtering, groupingsfrontendTheme parser epic label: frontendperformanceTheme parser epic label: performanceuiUser interface components and widgets

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions