perf(pwsh/psmux): cut per-pane cold-start cost in splits#78
Conversation
Investigation of "every tool init costs ~200ms" found Get-InitCache was NOT missing/falling back (zero cache misses on a full profile load). The cost splits into (1) dot-sourcing the cached init (~350ms starship / ~210ms atuin / ~195ms mise) which is cold-process JIT, not the cache, and (2) ~60ms/tool of validation-on-hit (Get-Command exe lookup + mtime reads). Get-InitCache (core/10-tools.ps1): - psmux fast-path: in a split, if the cache file exists, return it unvalidated (splits inherit the parent env, so the exe/generator can't have drifted) — skips the ~60ms/tool validation. Uses Test-InMux, added to the fragment's requires contract. - hidden DOTFILES_INITCACHE_DEBUG=1 instrumentation: prints a per-tool breakdown (Get-Command vs mtime timings + stale reason) on a cache MISS/throw. Scaffolding for the investigation; delete once settled. mise (core/10-tools.ps1): use `mise activate --shims` in psmux splits (distinct 'mise-shims' cache name so it doesn't thrash the top-level cache). A split inherits the parent's resolved env, so shims give correct versions at ~12ms vs ~195ms for the full hook-env init. Warm split mise cost measured 261ms -> 16ms. psmux.conf: flip `warm off`->`on` (psmux's default) and `destroy-unattached on`->`off` so the hidden __warm__ standby server can spawn (verified in an isolated -L namespace). Trade-off: detached sessions now persist and can pile up (manage via psmux ls / kill-session). Full effect lands on the next fresh psmux launch, not the running server. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The DOTFILES_INITCACHE_DEBUG instrumentation confirmed the cache is healthy (zero misses on a full profile load) and located the cost, so drop it. The psmux fast-path and mise-shims-in-splits changes stay. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce per-pane PowerShell cold-start latency when creating psmux splits by avoiding repeated init-cache validation work and by using faster mise “shims” activation in mux panes. It also adjusts psmux defaults to allow warm standby shells, moving unavoidable JIT costs off the split keypress.
Changes:
- Add a mux-oriented fast-path and debug instrumentation to
Get-InitCacheincore/10-tools.ps1. - Switch mise activation to
mise activate --shimsin mux panes with a separate cache key (mise-shims) to avoid cache thrash. - Enable psmux warm pooling and disable
destroy-unattachedto allow the hidden__warm__standby server.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
psmux/psmux.conf |
Enables warm pooling and changes detached-session behavior to support the warm standby server. |
powershell/core/10-tools.ps1 |
Optimizes init-cache behavior for mux panes and speeds up mise activation in splits via shims. |
Comments suppressed due to low confidence (1)
powershell/core/10-tools.ps1:167
- The mux fast-path returns an existing init-cache file without validating either the tool binary mtime or the generator-hash marker. Because $DotfilesInitCacheDir lives under LOCALAPPDATA and survives across psmux/tmux server restarts, this can serve stale init scripts after a tool upgrade or a repo/profile update (the exact cases the mtime/hash validation is meant to catch).
# the hash, so the stale cache self-busts on the next shell
# instead of silently serving the old init until someone
# remembers `init-cache-clear` (B2).
$genHash = if (Get-Command Get-DotStringSha256 -ErrorAction SilentlyContinue) {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # destroy-unattached OFF (flipped 2026-07-01, paired with `warm on` below): `on` made a | ||
| # detached session die immediately, which also SUPPRESSES psmux's hidden `__warm__` | ||
| # standby server (docs/warm-sessions.md) — so instant new-session never kicked in. Off |
Addresses PR review: the split fast-path previously returned the cached init unvalidated, which silently broke the B2 self-bust-on-flag-change invariant — and with warm/persistent sessions the LOCALAPPDATA cache can outlive many profile edits, so a split could serve a stale init after a generator (init-flag) change. Keep the CHEAP generator-hash check (SHA of a short string + one first-line read; touches neither PATH nor the exe) in the fast-path, and skip only the EXPENSIVE half (Get-Command exe resolution + Get-Item mtime — the AV-scanned ~60ms/tool this optimizes away). The rarer tool-BINARY upgrade mid-session stays uncaught in a split by design; it self-heals on the next top-level shell / fresh psmux server, or `init-cache-clear` forces it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @copilot-pull-request-reviewer — good catch on the fast-path skipping validation. Addressed in 500a373. The two cases you flagged have very different cost/likelihood, so I split them:
Verified: fast-path still hits on a hash match, and now regenerates on a generator change in a pane; 72/72 LoadContract tests pass. |
Addresses PR review: the destroy-unattached comment pointed at `docs/warm-sessions.md` as if it were a file in this repo. It lives in the upstream psmux project, not here — make that explicit with the repo URL so the pointer isn't a dangling local reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Fixed in the latest commit — |
What & why
Investigation of "every tool init costs ~200ms" found
Get-InitCachewas not missing or falling back — zero cache misses on a full profile load. The real cost splits into:Get-Commandexe lookup + mtime reads) — this can be skipped in splits.(An earlier commit added a hidden
DOTFILES_INITCACHE_DEBUG=1instrumentation to reach these findings; it has since been removed now that the cache is confirmed healthy.)Changes
Get-InitCache(core/10-tools.ps1)Test-InMux(added to the fragment'srequirescontract).mise (core/10-tools.ps1)
mise activate --shimsin psmux splits (distinctmise-shimscache name so it doesn't thrash the top-level cache). A split inherits the parent's resolved env, so shims give correct versions at ~12ms vs ~195ms for the full hook-env init. Warm split mise cost measured 261ms → 16ms.psmux.conf
warm off→on(psmux's default) anddestroy-unattached on→offso the hidden__warm__standby server can spawn (verified in an isolated-Lnamespace). Trade-off: detached sessions now persist and can pile up — manage viapsmux ls/psmux kill-session. Full effect lands on the next fresh psmux launch, not the running server.Verification
LoadContract.Tests.ps1pass;10-tools.ps1parses clean; pre-commit validation passed.wtest____warm__.port).Revert
psmux experiment reverts cleanly to
destroy-unattached on+warm off(or$env:PSMUX_NO_WARM=1) if the session persistence/pileup isn't worth the speedup.🤖 Generated with Claude Code