perf(acpmux): stream session summaries into list snapshots

Merged
TE
8d ago
+395-53

Full session lists retained every expanded JSON summary while constructing the serialized snapshot. Stream one complete summary at a time into serialized row storage, sort compact typed keys, and assemble the same reply. Remote rows are captured as immutable shared handles under their mutex and serialized after releasing it. Replacements and removals keep captured versions alive without deep-copying discarded rows under that mutex.

Closed sessions, tools, unknown fields, stable ordering, paging, peer normalization, authorization, and existing pacing limits retain their behavior. Typed sort keys avoid allocating temporary JSON key objects during collection; paging keys are constructed after sorting. At 1,000 retained sessions, the final same-worker comparison lowers mean post-list RSS by 63.59 MiB (16.6%) and daemon CPU by 21.2%.

Leo: “how much RAM, how many CPU cores are we taking with all these things? Are there ways to make things more efficient?”

Performance

MetricBeforeAfterDeltaHarness
N=0: RSS after lists62.920 MiB63.777 MiB+0.857 MiB (+1.4%)daemon_memory
N=0: Highest recorded peak RSS62.920 MiB63.777 MiB+0.857 MiB (+1.4%)daemon_memory
N=0: CPU, three requests10.000 ms20.000 ms+10.000 ms (+100.0%)daemon_memory
N=0: Paced wall, three requests0.006 s0.007 s+0.001 s (+25.0%)daemon_memory
N=288: RSS after lists180.148 MiB146.908 MiB-33.240 MiB (-18.5%)daemon_memory
N=288: Highest recorded peak RSS180.148 MiB148.479 MiB-31.670 MiB (-17.6%)daemon_memory
N=288: CPU, three requests240.000 ms190.000 ms-50.000 ms (-20.8%)daemon_memory
N=288: Paced wall, three requests1.965 s2.244 s+0.280 s (+14.2%)daemon_memory
N=1000: RSS after lists382.361 MiB318.771 MiB-63.590 MiB (-16.6%)daemon_memory
N=1000: Highest recorded peak RSS389.398 MiB324.008 MiB-65.391 MiB (-16.8%)daemon_memory
N=1000: CPU, three requests685.000 ms540.000 ms-145.000 ms (-21.2%)daemon_memory
N=1000: Paced wall, three requests7.399 s6.833 s-0.566 s (-7.6%)daemon_memory
Replay RSS after pages91.848 MiB93.598 MiB+1.750 MiB (+1.9%)daemon_page_memory
Replay peak RSS106.066 MiB107.918 MiB+1.852 MiB (+1.7%)daemon_page_memory
Replay CPU2430.000 ms2530.000 ms+100.000 ms (+4.1%)daemon_page_memory

List metrics report arithmetic means of two before and two after passes in a single Linux job, alternating before/after/before/after on one worker. Identical final-head benchmark code runs against baseline 69540503d2a4905a673fa58c8ab3b2c22cdd9521 and final 926608c4640bb26e67685947ac694a7edf31fa0f; the production files and benchmark blob hashes are checked for every pass. Binaries use the repository's test profile. Each N has two-thirds closed sessions and 24 tools per session. Logs are prepared through per-session info before three full-list requests; tags invalidate nonempty snapshots between requests. Peak is the highest recorded VmHWM, including intermediate request samples, reset before each phase with Linux clear_refs=5. CPU has clock-tick resolution. N=0 includes startup activity and can reuse the empty snapshot. These are repeated measurements, not confidence intervals or a macOS/3,000-session claim.

Is the paced wall time user-visible?

Yes. The benchmark calls the real _acpmux/sessions RPC. server/list_reply.rs:52-60 awaits the same guard used by sessions, watch initialization/reconnection, and ACP list. Initial app session lists depend on that snapshot. Home uses mirrored state rather than requesting a full list on every render. A new CLI connection does not itself incur the third-request connection penalty, though it still shares global build/reply pacing.

The three-request total includes RPC waits, tag invalidations, client decoding, validation, and reserialization. The unchanged guard admits two replies per connection, then charges a full reply one window; its window remains build elapsed time × 32, capped at 15 seconds. It does not admit fewer rows per tick or charge extra for serialized rows. Per-request timings separate the first two replies from the paced third reply:

N=1000 observed samplesBefore, pass 1 / pass 2After, pass 1 / pass 2
Reply 1 (server wait + transport)214.3 / 224.2 ms187.1 / 191.0 ms
Reply 2 (server wait + transport)217.9 / 233.6 ms179.0 / 171.0 ms
Reply 3 (server wait + transport)6329.3 / 6448.1 ms5635.3 / 6167.8 ms
Total paced wall7.309 / 7.489 s6.576 / 7.091 s
RSS after lists393.426 / 371.297 MiB321.516 / 316.027 MiB

The earlier 5.97 → 11.38 s sample at the interim implementation was a real regression signal and was not dismissed as benchmark-only time. That run lacked per-request timing, so the exact cause of its ratio cannot be reconstructed. The typed-key follow-up reduces allocations in the measured build path; the final repeated, same-worker comparison above establishes the current result within this harness. Previous comparisons across different workers are superseded and are not used to claim timing improvements. Existing pacing can still delay sustained list traffic by seconds; this change does not relax its limits.

Validation

  • Linux run 40785834614: SUCCESS, exact source 926608c4640bb26e67685947ac694a7edf31fa0f (snapshot 6dfe5649bad280560cebf386f91c2096344be275). Four alternating list-benchmark passes, before/after page replay, 26 focused unit tests, all four daemon-memory guards, and cargo clippy --locked -p acpmux --all-targets -- -D warnings passed.
  • The unchanged 400-chat / 7,500-purged-leftover guard passed at 158 MiB, below its unchanged 166 MiB limit. Replay reads 93,816 records / 44 pages from a 40 MiB log; its single before/after sample is included without claiming a replay improvement.
  • Independent Codex review: SHIP, no findings, at 926608c4640bb26e67685947ac694a7edf31fa0f.
  • Primary review checked the final diff against existing ordering, filtering, cursor, guard, and remote-row semantics. The crash-ratchet failure from two newly added expect calls was fixed by retaining the original non-panicking serialization handling.

Byte-equivalence coverage includes stable ties, duplicate IDs, escaped/Unicode strings, closed rows, tools, unknown fields, pages, and filters. The remote-row test removes every row inside the visitor, checks Arc identity/write counts, and verifies a complete captured snapshot with the mutex released.

Masked peer and ACP variants still materialize summary trees and remain outside this optimization. No timers were added; existing snapshot expiration and connection-pacing cleanup remain unchanged. The synthetic Linux harness does not establish memory savings on macOS.

Changelog: Reduce temporary daemon allocations while building session lists.
No screenshots: daemon-only change with byte-identical replies.

Agent session: agent session