Skip to content

perf(ui): render only the visible page of address-tab rows - #93

Merged
amanusk merged 1 commit into
masterfrom
perf/windowed-address-rows
Sep 13, 2026
Merged

amanusk merged 1 commit into
masterfrom
perf/windowed-address-rows

Conversation

@amanusk

@amanusk amanusk commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Problem

Every frame built a ListItem for every row of the active address tab (Txs, Calls, MetaTxs, Events), each with several linear registry lookups, ~10 format! allocations and a Utc::now(). Ratatui then painted ~55 of them.

Since run_loop redraws after every terminal event, each keystroke paid a full frame, and a pasted address arrives as one key event per character. On a contract with ~52k cached calls and ~760 user labels a frame cost ~170 ms: pressing / lagged, and a 66-char paste took ~11 s and appeared character by character. The main thread had burned ~117 CPU-seconds in 9 minutes while all tokio workers together used ~7.

Change

  • ListWindow (src/ui/widgets/list_window.rs) mirrors ratatui's List offset rule for single-line rows (no scroll padding): keep the stored offset unless the selection leaves the page, clamp to the list. Callers build rows for window.range() only; render() draws with the selection rebased to the window and writes the absolute offset/selection back into the persistent ListState, so txs_render_state.offset() consumers (enrichment window) keep working.
  • The four tabs in src/ui/views/address_info.rs render through it. Row bodies moved into tx_row / call_row / meta_tx_row unchanged; list chrome deduped into list_block / highlighted_list.
  • Gap rows keep their rendered positions via a shared rendered_row() (src/app/views/address_info.rs), which apply_rendered / apply_call_rendered now use instead of a private inverse mapping. New *_rendered_selected_with(&gaps) variants avoid recomputing gap positions twice per frame.

Bench

Release profile, 220x60 TestBackend, real ui::draw, synthetic rows (temporary example, not committed):

Scenario Before After
Calls tab, 52k rows, 759 labels 170 ms / frame 0.7 ms / frame
Calls tab, 52k rows, 0 labels 79 ms / frame 0.6 ms / frame
Calls tab, 500 rows, 759 labels 1.9 ms / frame 0.7 ms / frame
Txs tab, 52k rows, 759 labels — 1.1 ms / frame

Frame cost is now flat in list size. Verified interactively on a large-history contract: search and paste are immediate.

Tests

  • Unit tests for ListWindow::new (empty, clamped offset, selection above/below page, tail page) and for rendered_row (interleaving, round-trip with tx_pos_to_rendered).
  • cargo test, cargo clippy --all-targets (no new warnings), cargo build --release.

Follow-ups

🤖 Generated with Claude Code

Every frame built a ListItem for every row of the active address tab,
with several linear registry lookups per row. On a contract with ~52k
cached calls and ~760 user labels a frame cost ~170 ms, so each
keystroke (and each pasted character) paid it.

Add ListWindow, which mirrors ratatui's List offset rule for single-line
rows, and build rows only for that window in the Txs, Calls, MetaTxs and
Events tabs. Gap rows keep their rendered positions via a shared
rendered_row() mapping that the selection code now uses too.

Bench (release, 220x60 TestBackend, 52k rows / 759 labels):
Calls tab 170 ms -> 0.7 ms per frame; Txs tab 1.1 ms; flat in row count.

Follow-ups: #90 (O(1) registry lookups), #91 (input priority in the
main loop), #92 (per-WS-event O(n) work).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@amanusk amanusk added performance Performance: latency, throughput, resource usage snbeat Main snbeat TUI binary labels Sep 13, 2026
@amanusk
amanusk merged commit 612ad34 into master Sep 13, 2026
2 checks passed
@amanusk
amanusk deleted the perf/windowed-address-rows branch September 13, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Performance: latency, throughput, resource usage snbeat Main snbeat TUI binary

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant