ADR-002: Shell Performance Optimization Strategy¶
Status: Accepted Date: 2026-02-09 Authors: @sebastienrousseau
Context¶
Shell startup time directly impacts developer productivity. Every new terminal, tmux pane, or shell command execution incurs this cost. With rich shell configurations (completions, prompts, plugins), startup can easily exceed 1-2 seconds.
Goals:
- Target startup time: <500ms for interactive shells
- Maintain full functionality (completions, syntax highlighting, git info)
- Support both zsh and bash
- Work across macOS and Linux
Decision¶
Implement a multi-layer performance optimization strategy:
Layer 1: Compilation and Caching¶
# Compile zsh files to .zwc format
_cached_eval() {
local cache="$HOME/.cache/zsh/$1.zwc"
if [[ ! -f "$cache" || "$2" -nt "$cache" ]]; then
eval "$($2)" > "$cache.tmp"
zcompile "$cache.tmp" "$cache"
fi
source "$cache"
}
- Compile frequently-sourced files to bytecode
- Cache command output (brew shellenv, mise activate)
- Invalidate cache when source files change
Layer 2: Lazy Loading¶
Defer loading of heavy components until first use:
# Lazy load completions
function kubectl() {
unfunction kubectl
source <(kubectl completion zsh)
kubectl "$@"
}
- Completions loaded on first command use
- NVM/RVM loaded only when node/ruby commands invoked
- Heavy plugins deferred via zinit's
waitmodifier
Layer 3: Zinit Turbo Mode¶
- Plugins load asynchronously after prompt
- Critical plugins (syntax highlighting) load synchronously
- Most plugins have 0ms impact on startup
Layer 4: Conditional Loading¶
# Only load if command exists
[[ -x /opt/homebrew/bin/brew ]] && eval "$(/opt/homebrew/bin/brew shellenv)"
# Skip in non-interactive shells
[[ $- != *i* ]] && return
- Platform-specific code guarded by OS detection
- Heavy features opt-in via environment variables
- Non-interactive shells get minimal config
Monitoring¶
Benchmark script to track startup time:
CI enforces 500ms threshold with warnings.
Consequences¶
Positive¶
- Consistent <500ms startup across platforms
- Full functionality preserved
- Easy to add new tools without performance regression
- Clear patterns for contributors to follow
Negative¶
- First invocation of lazy-loaded commands is slower
- Cache invalidation bugs can cause stale behavior
- Complexity in understanding load order
Neutral¶
- Profiling required when adding new plugins
- Trade-off between convenience and performance explicit
Measurements¶
| Configuration | Startup Time |
|---|---|
| Vanilla zsh | ~50ms |
| With oh-my-zsh | ~800ms |
| This approach | ~200-400ms |