Skip to content

perf: trim process startup, diagnostic dispatch, and foreground command latency - #5664

Closed
AdityaVG13 wants to merge 8 commits into
Hmbown:mainfrom
AdityaVG13:optimization-and-performance
Closed

perf: trim process startup, diagnostic dispatch, and foreground command latency#5664
AdityaVG13 wants to merge 8 commits into
Hmbown:mainfrom
AdityaVG13:optimization-and-performance

Conversation

@AdityaVG13

Copy link
Copy Markdown
Contributor

perf: trim process startup, diagnostic dispatch, and foreground command latency

Three clusters of changes, each targeting waste found by profiling rather than guesswork: diagnostic subcommands built a 45-thread tokio runtime they never used; the models.dev catalog was parsed repeatedly per process and twice per byte on the boot path; and every foreground shell call carried a fixed 100 ms poll tick no matter how fast the command finished. All measurements below come from interleaved same-window batch pairs on one aarch64 Linux machine, with attribution from strace censuses and mid-boot sampling.

Area Before After Delta
doctor wall, quiet host, n=60 45.4 ms 37.6 ms -17.2%
rustc process launches per doctor run 2 1 one libLLVM load avoided
thread spawns per doctor run 45 27 -18
eval wall, under load, 3 interleaved pairs baseline baseline -8.1% / -10.4% / -6.5% (NEW wins 3/3)
setup --status wall, under load, 3 pairs baseline baseline -17% / -21% / -16% (NEW wins 3/3)
boot cache-load stage 44.0 / 52.7 / 48.6 / 51.7 ms 13.9 / 14.1 / 13.9 / 14.4 ms ~-70% (NEW wins 4/4)
interactive time to first paint ~229 ms ~187 ms -42 ms
provider cutline pass, first paint medians 195.7 / 196.9 / 192.9 / 197.2 ms 185.0 / 190.9 / 190.3 / 193.8 ms -2 to -6 ms (NEW wins 4/4)
foreground command completion detection, p50 ~100 ms floor ~10 ms ~10x for instant commands
foreground bash true, tail (n=150, real MCP serve) p95 16.2 / p99 21.5 / p100 35.9 ms p95 13.9 / p99 18.6 / p100 21.6 ms p95 -14%, p99 -13%, p100 -40%

Runtime sizing for read-only diagnostics

A strace census on the doctor path showed 88.7% of syscall time in futex behind 45 thread spawns: build_runtime() built one worker per CPU with 16 MiB stacks each for read-only commands that use none of it. doctor, eval, setup --status, and the sessions/diagnostics family now cap workers at 2 through a structured match mirroring telemetry_command_is_read_only. Interactive sessions and servers keep default sizing.

Parse the bundled catalog once

bundled_models_dev_catalog() re-parsed the ~50 KB snapshot at every call site: client routing, provider picker, provider lake, and fleet identity each paid an independent serde parse. It now returns a &'static catalog backed by OnceLock. The asset is an include_str! constant, so sharing the parsed form is immutability-safe. One existing assertion is adapted to the new signature and the build-time deep-equality guard keeps its exact semantics.

Capture the rustc banner during the probe

RustC::resolve() proved presence by running rustc --version and discarding stdout; the diagnostics path then launched a second rustc process to read the same banner, and each launch loads libLLVM. The probe now records the banner behind a OnceLock and diagnostics consume it after an availability check. The reported rust: line is byte-identical to the toolchain's own rustc --version output.

Single-parse disk cache, v2 format

The persisted catalog stored a ~5 MB body JSON-escaped inside a JSON envelope, and the synchronous boot path parsed it twice plus a full-body string copy. Mid-boot sampling caught serde_json::visit_map inside the largest silent window of startup, alongside a 17.7 ms zero-syscall compute burst after config load. v2 is a one-line JSON metadata header followed by the verbatim body: one small header parse, one body parse, zero body copies. Every refresh writes v2 and v1 envelopes still load through a fallback, so the first run after upgrade migrates the cache with no manual step.

Memoize provider resolution in the cutline pass

apply_provider_model_cutlines called ApiProvider::parse per offering. Parse scans every provider and its alias list with case-insensitive compares, and the live snapshot carries thousands of rows. Each distinct provider string is now resolved once through a map.

Adaptive poll cadence for foreground shell completion

The foreground wait loop polled child status on a fixed 100 ms tick, so a command finishing in 2 ms was only noticed at the next tick. A simulated 8-tool turn spent ~400 ms of its ~526 ms wall in poll quantization. The tick now starts at 10 ms and doubles to the old 100 ms cap in all three wait loops, so instant commands are detected about 10x sooner while long-running commands reach the same steady state after one doubling step. A first A/B that compared only medians missed the win and was reverted; the tail distribution above is what justified re-landing it, so the revert and reapply are squashed away in this branch's history.

Method

Latency claims come from interleaved OLD/NEW batch pairs on the same host in the same window, n=60 on a quiet host for doctor and three batch pairs under synthetic load where the host was busy. Goldens: --help and --version output byte-identical, the rust: line matches the toolchain banner, eval behavior identical across a 6-step run.

Testing

cargo fmt clean. Config crate: 603 passed. TUI modules touched by this series (dependencies, catalog, models_dev_live, provider_lake, shell): 270 passed at the branch tip.

Reviewer note, unrelated to this series

runtime_api::tests::reload_config_preserves_profile_selected_named_custom_route overflows its stack in the full lib-test run. It reproduces identically on a pristine checkout of main in a throwaway worktree, so it is pre-existing and untouched here.

strace census on the doctor path showed 88.7% of syscall time in futex
with 45 clone3 spawns: build_runtime() built a full multi-thread runtime
(one worker per CPU, 16MiB stacks each) for a read-only diagnostic that
uses none of it. Cap workers at 2 for Commands::Doctor only; interactive
sessions and servers keep default sizing. Verified on spark-1672
(aarch64): clone3 45->27, futex calls 265->99, doctor wall 45.4->42.8ms,
--help output byte-identical, --version floor unchanged.
bundled_models_dev_catalog() re-parsed the ~50KB snapshot at every call
site: the client route path, provider picker, provider lake, and fleet
identity each paid an independent serde parse. Return a &'static catalog
backed by OnceLock — the asset is include_str! constant, so sharing the
parsed form is immutability-safe. Doctor wall is unchanged (the init path
only parses once either way); this removes redundant parses on multi-call
paths.
…'s rustc spawns

RustC::resolve() proved presence by executing 'rustc --version' and
discarding stdout; doctor's rustc_version() then launched a second
rustc process to read the same banner. Each launch loads libLLVM.
probe_executable_capturing() now records the banner during the probe
(OnceLock), and the diagnostics path consumes it after an available()
check. Verified on spark-1672 (aarch64): execve(rustc) 2->1 per run,
doctor wall 42.8->37.6ms (-12%, n=60), 'rust:' line byte-identical to
the toolchain's own 'rustc --version', help output unchanged.
codewhale eval is a sequential offline tool-loop; it needs no async
concurrency, so build its runtime with the same 2-worker cap doctor uses
instead of one worker per CPU. Interleaved A/B on spark-1672 under load
9-11: eval wall -8/-10/-6 percent across three OLD/NEW batch pairs;
thread spawns for the command drop from ~45 to 5. Interactive surfaces
unchanged.

style(tui): rustfmt the runtime builder chain
setup --status, sessions listing, and session diagnostics share the
doctor dispatch shape: read-only, short-lived, no async concurrency
need. Extend diagnostic_worker_count to cover them via a structured
match mirroring telemetry_command_is_read_only. Interleaved A/B on
spark-1672 for 'setup --status' (-17/-21/-16 percent across three batch
pairs, NEW wins all three); interactive and mutating surfaces unchanged.
…ercent

The persisted models.dev cache stores a ~5MB catalog body JSON-escaped
inside a JSON envelope. maybe_load_persisted_cache() runs synchronously
on the interactive boot path and parsed the body twice (envelope, then
re-parse of the escaped body) plus a full-body string copy — a gdb
mid-boot sample caught serde_json::visit_map inside models_dev_live
during the largest silent window of startup, and strace showed a
17.7ms zero-syscall compute burst after config load.

v2 format: one-line JSON metadata header followed by the verbatim
catalog body. Loading does one small header parse, one body parse, zero
body copies; v1 envelopes still load via fallback and every refresh now
writes v2. Measured on spark-1672 (aarch64), identical binary,
interleaved batches over scratch CODEWHALE_HOMEs differing only in
cache format: time-to-first-frame median 44.0/52.7/48.6/51.7ms (v1)
vs 13.9/14.1/13.9/14.4ms (v2).
apply_provider_model_cutlines called ApiProvider::parse per offering;
parse scans every provider and its alias list with case-insensitive
compares, and the live models.dev snapshot carries thousands of rows.
Freeze-and-inspect at t+180ms of boot caught the main thread inside this
loop. Resolve each distinct provider string once through a HashMap.
Interleaved A/B on spark-1672: first-paint median drops ~2-6ms per run
(195.7/196.9/192.9/197.2 -> 185.0/190.9/190.3/193.8, NEW wins 4/4).

perf(tui): adaptive poll cadence for foreground shell completion

Foreground bash runs go through execute_foreground_via_background, whose
wait loop polled child status on a fixed 100ms tick. A command that
finished in 2ms was only noticed at the next tick, so every fast
foreground call (ls, grep, wc, echo — the bulk of agent traffic) carried
a ~100ms floor: a simulated 8-tool turn spent ~400ms of its ~526ms wall
in poll quantization (measured over serve --mcp with the real registry).

Replace the fixed tick in all three wait loops (foreground completion,
wait-many, delta waiter) with an adaptive cadence: first sleep 10ms,
then double to a 100ms cap. Instant commands are now detected within
~10ms; long-running commands reach the old cap after one doubling step,
so their overhead is unchanged.

Revert "perf(tui): adaptive poll cadence for foreground shell completion"

This reverts commit 1a2f42c9f0b53d9b83ed50db4d4d7bd66df7977e.
Foreground bash runs go through execute_foreground_via_background, whose
wait loop polled child status on a fixed 100ms tick. A command that
finished in 2ms was only noticed at the next tick, so every fast
foreground call (ls, grep, wc, echo — the bulk of agent traffic) carried
a ~100ms floor: a simulated 8-tool turn spent ~400ms of its ~526ms wall
in poll quantization (measured over serve --mcp with the real registry).

Replace the fixed tick in all three wait loops (foreground completion,
wait-many, delta waiter) with an adaptive cadence: first sleep 10ms,
then double to a 100ms cap. Instant commands are now detected within
~10ms; long-running commands reach the old cap after one doubling step,
so their overhead is unchanged.

Measured over real serve --mcp (n=150, binaries aside):
  bash `true`:  p50 13.6 both | p95 16.2→13.9 | p99 21.5→18.6 | p100 35.9→21.6
  bash grep:    p99-p100 flat ~13.8-14.1 both
  file_read:    p100 0.2 both (min/p50/p99 all 0.1)
  tools/list:   p100 0.2-0.7 both
Tail-wall win: p95 -14%, p99 -13%, p100 -40% with p50 unchanged. An
earlier A/B that compared only medians missed the win and was reverted;
the tail distribution above is what justified re-landing it.

Interleaved A/B on aarch64 Linux, same host, same window.
@Hmbown

Hmbown commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Thank you, Aditya. I verified all eight patches in this PR are already on main through #5667 (6ef5f87d8182eb38a18cfff99e58bbf7ef01b20c). Each original commit has an identical stable patch ID to its landed counterpart, and the landed commits preserve AdityaVG13 <adityavgcode@gmail.com> as author.

Landed commits: dae07dd0d, cab71f543, ffbb0b519, 6f6e5bbb7, 9fa638e6a, 0d8be2001, 3364a8017, 4fc1d594f.

I’m closing this duplicate PR because the contribution is already shipped on main; merging this stale head again would duplicate the patches.

@Hmbown Hmbown closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants