Skip to content

epic(perf): steady 120 Hz without drops (profile findings, Sep 2026) #987

Open

Description

@ZhuchkaTriplesix

Goal

Steady 120 Hz without drops (and headroom for 144 Hz): every frame ≤ 8.3 ms build and ≤ 8.3 ms raster, no multi-frame hitches on common actions.

How it was measured

Profile builds on Linux (Hyprland, 120 Hz monitor, AMD iGPU, Impeller GLES), two harnesses added in this investigation:

  • benchmark/app_perf_bench.dart: the real shell driven through the Demo Playground with a throwaway data directory and in-memory secrets. Scenarios: idle, sidebar toggle, run a 5000-row query, grid vertical / horizontal scroll, hover, typing, tabs, connections tree with 1500 tables (expand / scroll / collapse). Reports FrameTiming, presented-frame intervals from the raster timeline, and optionally CPU profile, allocations and engine timeline per scenario.
  • benchmark/grid_perf_bench.dart: the grid alone (5000×120), modes scroll, edit, select, dialog, rerun.

Caveats found along the way: recording the Dart timeline stream allocates heavily inside the framework and inflates GC; the in-process VM service client itself costs CPU; FrameTiming.buildDuration does not include flushSemantics.

Results (real app, window 2056×2616)

Scenario build p50 / max raster p50 Verdict
Idle nothing drawn — ok
Sidebar toggle 4.0 / 7.1 ms 0.8 ms too heavy for 144 Hz → #984
Run 5000-row query — / 54–75 ms 1.0 ms one big hitch → #982, #981
Grid vertical scroll 1.4 / 3.4 ms 0.8 ms ok
Grid horizontal scroll 0.1 / 12–20 ms 0.8 ms window-shift hitches → #983
Hover over grid 0.1 / 0.3 ms 0.8 ms ok
Typing in editor 2.6 / 5.0 ms 0.9 ms ok
Tab switch — / 6.8–7.9 ms 0.9 ms tight → #985
Tree 1500 tables: expand / scroll / collapse ≤ 5.9 / ≤ 2.8 / ≤ 1.8 ms 1.2–1.3 ms ok

All scenarios present at exactly 60 fps on Linux regardless of cost → #980 (engine).

Children

Added after the Oct 2026 performance review:

Already fixed in this cycle: #875, #879, #885, #972 (grid rebuilds), #874, #876, #877, #878, #886.

Related

#723 (connections tree @ 120 Hz: measured within budget with 1500 tables), #739 (QA sign-off).

Activity

  1. added a commit that references this issue on Sep 25, 2026
  2. ZhuchkaTriplesix commented on Sep 27, 2026

    @ZhuchkaTriplesix
    MemberAuthor

    Status update — 2026-09-27

    Went through all six children this cycle. Summary of what shipped, with measured before/after where it exists, and honest notes on where a fix didn't move the needle or wasn't attempted at all.

    Closed

    Still open

    Net effect

    Every scenario in the original profile table has a closed or measurably-improved issue against it except the sidebar toggle (partially improved, real bottleneck untouched) and the Linux vsync cap (untouched, may not be fixable here at all). Given the remaining work is either high-risk-for-uncertain-gain (#984's Row restructure) or possibly not our issue to fix (#980's embedder cap), pausing here rather than continuing to grind on partial increments — flagging both for a deliberate decision on whether/how to pursue them next.

  3. ZhuchkaTriplesix commented on Oct 12, 2026

    @ZhuchkaTriplesix
    MemberAuthor

    Re-measurement on dev/main (2026-10-12)

    benchmark/app_perf_bench.dart, profile build, Linux, 120 Hz display, window 2056x2616, 5000 rows x 20 cols, tree of 1500 tables.

    Scenario build p50 / max (ms) raster p50 (ms)
    sidebar toggle 2.2 / 3.9 1.0
    run 5000-row query 10.9 / 50.5 (4 frames) 1.2
    grid scroll vertical 1.1 / 1.7 0.7
    grid scroll horizontal 0.1 / 7.8 0.7
    hover over grid 0.1 / 0.2 0.7
    typing in editor 2.5 / 3.2 0.8
    tree expand / scroll / collapse (1500) 0.5 / 0.8 / 0.6 (max 4.9 / 1.6 / 1.3) 0.8-0.9
    new tab and switch 1.1 / 3.6 0.8
    run in 1 of 5 tabs 0.2 / 6.7 (1 pane rebuilt) 0.9

    Reading:

  4. ZhuchkaTriplesix commented on Oct 12, 2026

    @ZhuchkaTriplesix
    MemberAuthor

    Follow-up: where the run_heavy_query hitch comes from (call-path analysis, same machine and build)

    What was measured, and what turned out to be noise:

    1. Phase split of the hitch frame. With debugProfileBuildsEnabled / LayoutsEnabled / PaintsEnabled the timeline for the 5000 x 20 run shows BUILD 9.0 ms total (30 builds) and LAYOUT 53.4 ms in 7 frames, i.e. the ~50 ms frame is layout, not build. (These flags add per-object timeline events, so leaf numbers such as RenderParagraph 43 ms for 242 paragraphs are inflated; a clean paragraph layout is 10-20 us, see below.)
    2. Text layout is not the cause for Latin data. TextPainter.layout is 10-20 us per paragraph for ASCII / Cyrillic / Latin-extended in every font-family variant (monospace, a concrete installed family, a missing family, the 10-family editor stack). Only CJK is slow (~400 us, 40x); filed separately.
    3. Native font lookup (FcPatternGetString, FcStrCmpIgnoreCase, FcPatternFilter) in the CPU profile comes from that fallback path; replacing 'monospace' by a concrete family in the grid changed the max build only from 47-52 ms to 38-45 ms (n = 2 each), so it is not the main cost.
    4. GC on the UI thread is large. Without the Dart timeline stream (which inflates GC), one run_heavy_query scenario shows ~35 young-generation collections on the UI thread totalling ~200 ms (~5.7 ms each, i.e. a frame each at 120 Hz) plus 30-60 ms of old-generation work. That points at the allocation volume of the result path (List<List<Object?>>, per-cell strings), not at layout; see perf(database): CompactResultDataset.fromRawRows formats every cell during type detection; split it and sample #1548, perf(results): carry the columnar dataset to sort / filter / export instead of List<List<String>> (memory 68 -> 17 MiB at 50k x 20) #1549, perf(results): build typed columns directly from the driver stream instead of collecting List<List<Object?>> first #1552.
    5. Noise to ignore: in the app benchmark's CPU profile the JSON parsing, _OneByteString.==/hashCode and LinkedHashMap lookups are caused by the VM-service client itself (the call chains end in VmService._processMessage / createServiceObject).
    6. Ruled out: the PostgreSQL / MySQL decoders do not block the UI isolate (event-loop stalls 0-2 ms at 50k rows for PostgreSQL, 4-15 ms for MySQL), so moving drivers into a worker isolate would not remove a hitch; SQLite row[col] lookups cost 1.8 ms for 5000 x 20 (row.values is not faster).

    Note on Linux fps: a profile build from flutter run uses the stock engine (60 fps). Release builds splice the vsync-patched engine (scripts/linux/apply_patched_engine.sh), verified on the installed package.

  5. ZhuchkaTriplesix commented on Oct 12, 2026

    @ZhuchkaTriplesix
    MemberAuthor

    Memory finding from the same session as the CPU work: every result grid that is shown stays alive for the rest of the session (~5 MB per run at 5000 x 20; 6 re-runs -> 6 live grid states and datasets), and closed tabs retain their editor, results tab, grid and dataset. Root cause and the two-line fix are in #1555 (P1). Caches in lib/ were audited and are bounded (highlighter pair cache 12, intern pool 4096, connection pools LRU, Redis history 200); _gridSchemaCache is the only one without a cap but is cleared on writes and bounded by table count.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicEpic / umbrella issueperformanceTheme parser epic label: performance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions