Skip to content

Performance and resource-efficiency audit ​

Performance index · Reproduction playbook

Historical context: this report records the measured 2026-08-03 worktree. The current tree resolves tui-lipan and relswap from crates.io, so source and dependency details in this report are not current guidance. The measurements remain evidence for the recorded revision only.

Executive assessment ​

This dated audit found no performance problems in normal interactive use:

  • Rendering and layout stayed below 0.4 ms at 16 panes.
  • Terminal parsing sustained roughly 16–97 MiB/s depending on viewport and escape density.
  • For 128 small messages, lazy snapshot rebuilding cost 0.42 ms versus 15.5 ms for eager rebuilding.
  • Protocol framing reached several GiB/s and was not a bottleneck.
  • Transport queues, scrollback, semantic marks, images, and slow-client backlogs had bounds.
  • Inactive panes avoided redraws. Background sessions continued parsing for instant switching.

The audit found no release-blocking performance problems. It confirmed these concerns:

  1. "All panes" scrollback search blocked the UI for 68–81 ms at 16 panes × 5,000 lines.
  2. Idle polling consumed about 0.5–0.7% of one core in the client and 0.8–1.0% in each attached server.
  3. Workbar command pollers survived config removal and could accumulate across reloads.

Retained terminal state set the measured scalability limit. Eight large, full-history panes consumed about 265 MiB application PSS with one client and 372 MiB with two.

Verdict: suitable for release, with specific non-blocking improvements to consider.

Scope and environment ​

Measured revision: c07b6bef0bdc9527dc368aaef43ed1b8596e0bed, including the uncommitted audit worktree. The render benchmarks were rerun after the latest observed view/sidebar edits.

Environment:

ItemValue
OSArch Linux, kernel 7.1.4
CPUAMD Ryzen 7 5700X3D, 8 cores / 16 threads
Rustrustc 1.93.0
Release profileoptimized, thin LTO, one codegen unit, stripped
Release binary19,112,248 bytes / 18.23 MiB
Debug binary353,672,800 bytes / 337.3 MiB

Sampling profiling was unavailable. Samply was installed, and perf_event_paranoid=2 denied access. No system setting was changed. Valgrind, heaptrack, perf, pidstat, smem, and hyperfine were unavailable. The audit therefore did not collect allocation counts or sampled call-stack percentages.

Architecture and hot paths ​

At the measured revision, Rozi used an always-server architecture:

  1. A server owns PTYs and parses every pane into a server-side TerminalScreen.
  2. Raw pane bytes are framed once and broadcast using a shared Arc<[u8]>.
  3. Each attached client parses the bytes into its own screen.
  4. The root AppRoot processes messages and uses full, layout-only, paint-only, or no-update rendering.
  5. Background attachments stay connected and continue parsing. They do not render.

Performance-sensitive paths:

  • PTY output: SessionServer::handle_event → broadcast → InboundMailbox → update::session::output → TerminalPane::process_server_output.
  • Rendering: view::render, terminal snapshot rebuild, framework paint/diff.
  • Search: ops::search::recompute_search → TerminalPane::search_scrollback.
  • Session server loop: SessionServer::run_listener.
  • Runtime metadata: SessionServer::poll_pane_runtime.
  • Attach/replay: enqueue_attach_seeds and TerminalScreen::export_replay_bytes.
  • Resurrection: synchronous SessionServer::write_snapshot.

Baseline results ​

CPU and latency ​

Command/workloadModeSizeResult
cargo bench --bench app_render -- 'app_render/view_layout/(8|16)|sidebar_render'release8 panes, 200×60221.45 µs view/layout
Samerelease16 panes, 200×60385.64 µs
Same, Agents sidebarrelease8 panes, 200×60396.47 µs
cargo bench --bench snapshot_rebuildrelease200×60377.64 µs
Samerelease320×90892.11 µs
Output burst, 128×64-byte messagesreleaseone pane415.63 µs lazy versus 15.47 ms eager
Temporary search diagnosticrelease1 pane × 5,000 lines, 250×604.0–4.8 ms
Samerelease8 panes34.7–41.0 ms
Samerelease16 panes68.3–81.0 ms
Temporary picker diagnosticrelease10,000 entries3.17 ms
Same search diagnosticdebug16 panes1.68–1.83 seconds
Isolated /proc/<pid>/stat sample, 6 sreleaseidle client0.50–0.67% of one core
Samereleaseidle server0.83–1.00%
Scripted detach, five runsrelease250×6092–93 ms

The pseudo-terminal startup harness reached an input-ready pane in 791–793 ms. This was not reliable startup evidence because the non-responsive pseudo-terminal appeared to force the host-color query timeout. The result is excluded from the findings.

Throughput ​

From cargo bench:

OperationCaseThroughput
Terminal ingest, plain200×6023.0 MiB/s
Terminal ingest, SGR-heavy200×6027.5 MiB/s
Terminal ingest, scroll regions200×6058.9 MiB/s
Terminal ingest, wide Unicode200×6048.2 MiB/s
Terminal ingest, 1 MiB sparse lines200×6069.9 MiB/s
Full in-memory session pipeline4 KiB23.9 MiB/s
Full in-memory session pipeline64 KiB94.6 MiB/s
Unix socket pipeline4 KiB22.0 MiB/s
Pane frame round trip4 KiB7.39 GiB/s
Pane frame round trip1 MiB5.22 GiB/s

Protocol framing was much faster than parsing and rendering. The measurement did not justify optimizing it.

Memory ​

Commands:

bash
tools/memory-matrix.sh --case 60 250 1 1 plain 1
tools/memory-matrix.sh --case 60 250 1 5000 styled 1
tools/memory-matrix.sh --case 60 250 8 5000 styled 1
tools/memory-matrix.sh --case 60 250 8 5000 styled 2

Median of five PSS samples after a two-second settle:

WorkloadClient PSSServer PSSApplication PSSChild PSS
1 pane, minimal history15.0 MiB7.9 MiB22.9 MiB1.1 MiB
1 pane, 5,000 styled lines49.7 MiB39.8 MiB89.4 MiB1.1 MiB
8 panes, 5,000 styled lines136.2 MiB128.6 MiB264.8 MiB5.0 MiB
8 panes, two clients245.8 MiB125.8 MiB371.6 MiB5.0 MiB

Application PSS excludes child processes.

Idle resources:

  • Client: 18 threads, 24 file descriptors.
  • Server with one pane: 3 threads, 18 descriptors.
  • Eight panes and one client: 35 application threads.
  • Eight panes and two clients: 52 application threads.

Most were blocking transport, PTY, watcher, and command-worker threads. Thread count scaled without a proportional increase in idle CPU.

Confirmed findings ​

Medium: all-pane scrollback search blocks the UI ​

Affected workload: large scrollback, especially workspace/all-pane scope.

Evidence:

  • One pane: 4–5 ms.
  • Eight panes: 35–41 ms.
  • Sixteen panes: 68–81 ms.
  • Debug builds exceed 1.8 seconds at the largest case.

Relevant code:

  • src/ops/search.rs: recompute_search
  • src/pane.rs: TerminalPane::search_scrollback
  • src/view/overlays/search.rs: per-render construction of result entries

Root cause: every query synchronously exports all lines. It allocates Strings, lowercases every line, finds every match, and retains cloned matching text. Cost is linear in total retained text and match count.

Impact: visible input lag appears only at high pane/history scope. Focused-pane search is acceptable.

Recommendation: add a retained Criterion benchmark first. If 16-pane all-scope search is expected usage, add a streaming grid-search API to TerminalScreen. Avoid materializing all lines, and support cancellation/latest-query semantics.

Complexity: medium to high.

Trade-offs: caching lowercase text would reduce CPU and duplicate significant scrollback memory. Measure both effects before adopting it.

Verification: require p95 below 16 ms for the chosen supported scale, with sparse and dense matches.

Medium: optional workbar command tasks are not cancellable ​

Relevant code:

  • src/pane_lifecycle.rs: spawn_workbar_command_pollers
  • src/ops/config.rs: reload_config
  • src/update/overlays.rs: workbar_command_output

Evidence: each command owns a permanent loop. Removing it from config does not stop the thread. Changing its interval does not update the existing thread. Adding unique commands across reloads accumulates threads. Command::output() also has no timeout or output bound.

Impact: normally zero because workbar commands are optional. Repeated live reloads can leave obsolete commands running indefinitely or retain a hung command thread.

Recommendation: use a generation/cancellation token and the existing bounded shell-command helper. Remove stale output and running-state entries during config reconciliation.

Complexity: medium.

Expected improvement: eliminates orphan threads and process launches with no measured common-path cost.

Verification: reload from command A to no command, confirm thread/process count stabilizes and A never runs again.

Low: idle polling has a measurable fixed cost ​

Evidence:

  • Client: 0.50–0.67% of one core.
  • Server: 0.83–1.00% per attached server.

Relevant code:

  • src/session/server/mod.rs: SessionServer::run_listener sleeps 1 ms whenever a client exists.
  • tui-lipan/src/app/runner/animation_ticker.rs: focused terminals force a 16 ms wake interval.

The client interval supports 60 Hz output responsiveness. The server loop polls nonblocking IPC, PTY events, heartbeats, and queues.

Impact: small for one session. Each retained background session keeps a server in the 1 ms attached-client loop.

Recommendation: investigate readiness-based wakeups or adaptive server backoff. Preserve bounded input latency and output fairness.

Complexity: high for readiness polling, low/medium for adaptive backoff.

Verification: compare idle CPU and p50/p95 input latency with 1, 5, and 10 parked sessions.

Informational: each parser adds bounded memory ​

The server and every client hold independent terminal parsers and scrollback. Memory therefore scales roughly with:

text
panes × retained cells × (server + attached clients)

The measurements show the cost of duplicate parsing. They do not show a leak. Reducing that duplication would require a large protocol and state architecture change, which the measurements did not justify.

The limit affects workloads that combine large viewports, 5,000-line histories, many panes, multiple clients, or many retained background sessions.

Existing efficient paths ​

  • Output uses TerminalScreenHandle and Update::paint, avoiding whole-app view/layout work.
  • Snapshot rebuilding is lazy and burst-coalesced.
  • Inactive workspaces do not redraw from output.
  • Raw output is encoded once and shared across clients with Arc<[u8]>.
  • Slow clients are disconnected before they can block broadcast.
  • Client inbound/outbound and server outboxes are byte-bounded at 8 MiB.
  • Initial replay has a separate 64 MiB cap.
  • PTY ingress is bounded at 4 MiB and applies producer backpressure.
  • Adjacent output coalesces up to 64 KiB.
  • Protocol frames are limited to 8 MiB.
  • Scrollback, semantic marks, image storage, placements, and stderr capture are bounded.
  • Resize and layout commits are debounced.
  • Process-table scanning is shared across panes and agent detection is change-gated.
  • Clock, agent-duration, theme, and sidebar refresh ticks avoid unchanged redraws.
  • Framework input, resize, drag, and scroll events are coalesced.
  • Locks are short and localized. The thread-based application holds no locks across .await.
  • The release settings fit the measured workload. Fat LTO, panic=abort, and opt-level=z should remain opt-in size trade-offs.

Queue and backpressure summary ​

Queue/bufferBoundOverflow behavior
Server PTY ingress4 MiBproducer blocks
Server client outbox8 MiBslow client disconnected
Attach replay outbox64 MiBclient disconnected
Client inbound mailbox8 MiBtransport fails/reconnects
Client outbound queue8 MiBtransport fails
Output coalescing64 KiBstarts another entry
Protocol frame8 MiBrejected
Orphan output256 KiB per pane/generationoldest bytes dropped
Event subscriber128 messagessubscriber removed
Terminal images96 MiB decoded pixels per screenLRU eviction

Unverified risks ​

  1. src/platform/ipc/piped.rs has an unbounded VecDeque<u8> before the bounded client mailbox. Test a remote producer faster than its client for several minutes and assert a memory plateau.
  2. SharedSessionState::orphan_output is bounded per key and has no global bound. Stale pane generations may remain. Simulate repeated follower-side spawn/output/kill races and inspect retained keys.
  3. Resurrection snapshots synchronously export, write, and fsync every pane in the server loop. Measure p99 input/output latency during a 16-pane, 5,000-line snapshot on slow storage.
  4. The server may process up to 4 MiB of queued PTY data before servicing client input. Measure key round-trip latency during maximum output.
  5. Terminal images permit 96 MiB per parser, multiplied by panes and clients. Add an image-heavy memory matrix case.
  6. Background attachments have no count limit and continue parsing. Measure CPU/PSS with 1/5/10 active background sessions.
  7. Actual backend draw/diff time and frame-time percentiles were not measured. TestBackend capture allocates per cell and is unsuitable. Use interactive devtools metrics or Samply after lowering perf_event_paranoid.
  8. Long-duration leaks, reconnect churn, and memory after pane/session closure need a 1–24 hour soak. The attempted short cleanup run did not establish a valid populated-pane baseline. No leak conclusion is made.

Prioritized optimization plan ​

  1. Release-blocking

    • None.
  2. High-value

    • Make workbar command pollers cancellable and bounded.
    • Add a scrollback-search benchmark. Optimize the streaming path if broad-scope search is expected to meet one-frame latency.
    • Measure server fairness, then consider adaptive or readiness-driven idle waiting.
  3. Useful, non-urgent

    • Add remote-pipe and orphan-output global bounds.
    • Instrument queue high-water marks and resurrection duration.
    • Add configurable terminal-image budgets.
    • Add background-session CPU/PSS and cleanup workloads.
  4. Premature or unnecessary

    • Optimizing protocol serialization.
    • Compressing local output frames.
    • Replacing shared Arc broadcast frames.
    • Memoizing terminal snapshots further.
    • Splitting every pane into a child component solely for scoped rendering.
    • Micro-optimizing clones without allocation/profile evidence.
    • Changing the default release profile to fat LTO or size optimization.

Minimal regression suite ​

Keep the existing five Criterion targets and memory matrix. Add:

  • scrollback_search: 1/8/16 panes, 5,000 lines, 250×60, sparse and dense matches.
  • server_fairness: input latency while PTY ingress remains saturated.
  • background_sessions: CPU/PSS and switch latency for 1/5/10 sessions.
  • resurrection_snapshot: snapshot duration by pane count and scrollback.
  • remote_backpressure: bounded RSS with a stalled remote consumer.
  • Memory cleanup cases for pane close, client disconnect, session kill, and reconnect.

Timing benchmarks should remain opt-in on stable hardware. CI should compile them and run deterministic correctness and boundedness tests. It should not enforce noisy wall-clock thresholds.

No production files were modified by this audit. Temporary diagnostics were removed. Generated memory results remain under target/perf-audit/.

Verdict: ready with minor improvements

MPL-2.0