Investigate zsh interactive startup performance #12

Open
opened 2026-10-08 13:45:57 -06:00 by ripley-agent · 0 comments
Collaborator

Goal

Zsh interactive startup feels slow. Measure it, find the hotspots, and fix the biggest offenders.

How to measure

# Profile the whole startup (add `zmodload zsh/zprof` at the very top of .zshrc temporarily, `zprof` at the end)
zmodload zsh/zprof && source ~/.zshrc && zprof | head -30

# Or time raw startup without touching files
hyperfine --warmup 3 'zsh -i -c exit'

# Per-file timing for the zshrc.d loop
for f in ~/.config/zshrc.d/*; do hyperfine --warmup 1 "zsh -i -c 'source $f'" --minimum-runs 3; done

Suspected offenders (from reading the current config)

  1. Double compinit — .zshrc runs autoload -Uz compinit; compinit in the OpenSpec block, then Oh My Zsh (00-oh-my-zsh.zsh → oh-my-zsh.sh) runs its own compinit. That's two full completion-dump scans per startup. Consider compinit -C (skip dump check) or letting OMZ own it entirely.
  2. NVM loaded synchronously — . "$NVM_DIR/nvm.sh" in .zshrc is a classic 100–300 ms hit. Lazy-load it (stub nvm function that self-replaces on first call) or drop it if a system Node suffices.
  3. eval "$(direnv hook zsh)" — forks a process every startup; small but real. The OMZ direnv plugin (per the existing TODO) is marginally cheaper.
  4. gpgconf --list-dirs agent-ssh-socket — another fork per startup. The socket path is stable; cache it or set it once in .zshenv/environment.
  5. rbenv init — eval "$(rbenv init - --no-rehash zsh)" also forks. Same lazy-load treatment applies.
  6. Terminal-title precmd hook — _aidevops_terminal_title runs 3 git rev-parse forks per prompt, not just at startup. Worth folding into one git call or caching.
  7. zshrc.d glob loop — sources every file unconditionally; fine in itself, but makes per-file timing (above) the right way to spot anything unexpected hiding in 20-functions.zsh / 30-aliases.zsh / the extra-* package fragments.

Acceptance criteria

  • Baseline hyperfine 'zsh -i -c exit' number recorded in this issue
  • zprof output analyzed; top 3 offenders identified
  • Fixes applied for offenders costing >20 ms each
  • Post-fix hyperfine number recorded (target: <50% of baseline if NVM lazy-loaded)
## Goal Zsh interactive startup feels slow. Measure it, find the hotspots, and fix the biggest offenders. ## How to measure ```bash # Profile the whole startup (add `zmodload zsh/zprof` at the very top of .zshrc temporarily, `zprof` at the end) zmodload zsh/zprof && source ~/.zshrc && zprof | head -30 # Or time raw startup without touching files hyperfine --warmup 3 'zsh -i -c exit' # Per-file timing for the zshrc.d loop for f in ~/.config/zshrc.d/*; do hyperfine --warmup 1 "zsh -i -c 'source $f'" --minimum-runs 3; done ``` ## Suspected offenders (from reading the current config) 1. **Double `compinit`** — `.zshrc` runs `autoload -Uz compinit; compinit` in the OpenSpec block, then Oh My Zsh (`00-oh-my-zsh.zsh` → `oh-my-zsh.sh`) runs its own `compinit`. That's two full completion-dump scans per startup. Consider `compinit -C` (skip dump check) or letting OMZ own it entirely. 2. **NVM loaded synchronously** — `. "$NVM_DIR/nvm.sh"` in `.zshrc` is a classic 100–300 ms hit. Lazy-load it (stub `nvm` function that self-replaces on first call) or drop it if a system Node suffices. 3. **`eval "$(direnv hook zsh)"`** — forks a process every startup; small but real. The OMZ `direnv` plugin (per the existing TODO) is marginally cheaper. 4. **`gpgconf --list-dirs agent-ssh-socket`** — another fork per startup. The socket path is stable; cache it or set it once in `.zshenv`/environment. 5. **rbenv init** — `eval "$(rbenv init - --no-rehash zsh)"` also forks. Same lazy-load treatment applies. 6. **Terminal-title precmd hook** — `_aidevops_terminal_title` runs 3 `git rev-parse` forks *per prompt*, not just at startup. Worth folding into one `git` call or caching. 7. **`zshrc.d` glob loop** — sources every file unconditionally; fine in itself, but makes per-file timing (above) the right way to spot anything unexpected hiding in `20-functions.zsh` / `30-aliases.zsh` / the `extra-*` package fragments. ## Acceptance criteria - [ ] Baseline `hyperfine 'zsh -i -c exit'` number recorded in this issue - [ ] `zprof` output analyzed; top 3 offenders identified - [ ] Fixes applied for offenders costing >20 ms each - [ ] Post-fix hyperfine number recorded (target: <50% of baseline if NVM lazy-loaded)
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coleneville/dotfiles#12
No description provided.