β‘ ZSHAND Performance GuideΒΆ
π This guide covers performance targets, how to profile, how to read reports, and optimization strategies for keeping shell startup fast.
π§ Philosophy
Every millisecond counts at startup. The framework targets <50ms cold start and <10ms warm start. Profile before optimizing β gut feelings about performance are almost always wrong.
Run a profiled shell and see a graded report (which files are slow):
Or use zprofile for more options (zprofile --full, zprofile --report). See Profile Report Guide to read the report.
Measure cold start time (e.g. before/after a change):
Use a full bundle so one file loads instead of dozens: set ZSHAND_AUTO_FULL=1 in ~/.zshenv, or run zfull. Enable lazy hooks with ZSHAND_LAZY_LOAD=1 to defer non-critical tools. See Optimization strategies below.
π― Performance TargetsΒΆ
| Metric | Target | Grade A | Grade F | How to Measure |
|---|---|---|---|---|
| β‘ Cold start | <50ms | <50ms | β₯500ms | hyperfine 'zsh -ic exit' |
| β‘ Warm start (P10k) | <10ms | <10ms | >100ms | Instant prompt to interactive |
| π Per-file budget | <100ms | <50ms | β₯200ms | ZSHAND_PERF_BUDGET enforcement |
| π¦ Bundle load | <30ms | <20ms | >100ms | Profile bundle report |
| πͺ¨ Hook total | <50ms | <30ms | >150ms | Profile bundle report |
β Grading ScaleΒΆ
The framework uses letter grades for performance reports:
| Grade | Stars | Threshold | Meaning |
|---|---|---|---|
| A | β β β β | <50ms | Excellent β near-instant |
| A- | β β β β | <100ms | Great β barely perceptible |
| B | β β β | <150ms | Good β fast enough |
| B- | β β β | <200ms | Acceptable β room for improvement |
| C | β β | <250ms | Mediocre β optimize |
| C- | β β | <350ms | Slow β deferred loading recommended |
| D | β | <500ms | Poor β significant work needed |
| F | β₯500ms | Failing β major issues |
π Profiling PipelineΒΆ
π OverviewΒΆ
ZSHAND_PROFILE_BUNDLE=1 exec zsh β Start profiled shell
β
βΌ
compile_profile_bundle.zsh β Builds instrumented bundle
β
βΌ
$ZSHAND_CACHE_DIR/profile-timing.log β Raw timing data
β
βΌ
profile_bundle_report.zsh β Generates graded report
β
βΌ
Terminal: color-coded report β Grades, bar charts, bottlenecks
βΆοΈ Running a ProfileΒΆ
# Quick profile (shows report immediately)
ZSHAND_PROFILE_BUNDLE=1 exec zsh
# Full profile (includes widget profiling, deferred/lazy timing)
zprofile --full
# Profile and defer report (captures deferred hook data)
zprofile --report
# β Use the profiled shell normally for a few seconds
# β Exit the shell β report displays with deferred/lazy data included
π Reading the ReportΒΆ
The profile report shows timing data organized by directory:
β‘ ZSHAND Profile Report
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Total startup: 42.3ms Grade: A β
β
β
β
π Directory Breakdown:
shared_functions 3.2ms ββββββββββ 7.6%
startup 4.1ms ββββββββββ 9.7%
core 18.7ms ββββββββββ 44.2%
functions 5.3ms ββββββββββ 12.5%
widgets 2.1ms ββββββββββ 5.0%
hooks 8.9ms ββββββββββ 21.0%
π Slowest Files:
core/06_engine.zsh 8.4ms β Engine init (Atuin, security)
core/20_plugins.zsh 5.2ms β Plugin loading
hooks/01_mise.zsh 4.1ms β Runtime manager init
β‘ Deferred Operations:
hooks/20_docker.zsh 12ms (deferred β not in startup path)
hooks/50_gitid.zsh 8ms (deferred β loaded after 1s)
Key things to look for:
- π΄ Any single file >20ms β candidate for optimization
- π΄ Total >100ms β investigate top 3 slowest files
- π‘ Deferred items >50ms β consider if they need to load at all
- π’ Total <50ms β you're in great shape
π§ Optimization StrategiesΒΆ
1οΈβ£ Use Compiled BundlesΒΆ
The single biggest performance win. Instead of sourcing 50+ individual files, source one pre-compiled bundle:
# Compile everything into a single bundle
zprime --full
# Enable auto-full mode (compiles on first run, then loads bundle)
export ZSHAND_AUTO_FULL=1
Impact: Individual files ~200ms β Full bundle ~30ms (6β7x faster)
2οΈβ£ Defer Non-Critical HooksΒΆ
Hooks that aren't needed at first prompt can load after a 1-second delay:
Hook classification is defined in core/18_hooks.zsh:
- β‘ Critical (sync):
01_mise,02_atuin,05_ssh-agent - π Lazy (deferred):
20_docker,30_zoxide,50_gitid, etc.
Impact: Moves 20β80ms of hook loading out of the startup path.
3οΈβ£ Cache Expensive OperationsΒΆ
| Pattern | Example | Savings |
|---|---|---|
| ποΈ Cache command output | mise activate β cached .zsh file | ~15ms |
| ποΈ Cache network state | Ping check β 5-min TTL file | ~50ms |
| ποΈ Cache completions | gh completion β cached file | ~20ms |
| ποΈ Bytecode compile | .zsh β .zwc via zcompile | ~5ms per file |
4οΈβ£ Use Zsh Builtins Over External CommandsΒΆ
| Slow (subprocess) | Fast (builtin) | Savings |
|---|---|---|
$(date +%s%N) | $EPOCHREALTIME | ~3ms |
$(wc -l < file) | ${#${(f)"$(<file)"}} | ~2ms |
$(basename "$path") | ${path:t} | ~2ms |
$(dirname "$path") | ${path:h} | ~2ms |
$(stat -c %s file) | zstat -H arr file | ~2ms |
$(hostname -s) | ${HOST%%.*} | ~2ms |
if [ -d "$dir" ] | [[ -d "$dir" ]] | <1ms |
5οΈβ£ Avoid Network in Startup PathΒΆ
Network calls are the #1 cause of slow startup:
- π« Never
curl/wgetduring init - π« Never
pingsynchronously (cache the result) - π« Never
git fetchduring startup - β Defer network ops to background or hooks
- β
Cache network state with TTL (
$ZSHAND_CACHE_DIR/network_state)
6οΈβ£ Profile ExceptionsΒΆ
Some files are inherently slow (e.g., plugin managers). Add them to config/profile-exceptions.conf to suppress warnings:
# pattern | max_time_ms | reason
20_plugins.zsh | 15 | Plugin loading is inherently slow
01_mise.zsh | 10 | Runtime manager activation
π BenchmarkingΒΆ
β‘ Hyperfine (Recommended)ΒΆ
# Basic startup benchmark (3-run warmup, 10 runs)
hyperfine --warmup 3 'zsh -ic exit'
# Compare two configurations
hyperfine --warmup 3 \
'ZSHAND_AUTO_FULL=1 zsh -ic exit' \
'ZSHAND_DEV_MODE=1 zsh -ic exit'
# Export for comparison across commits
hyperfine --warmup 3 'zsh -ic exit' --export-json bench.json
# Single file load time
hyperfine --warmup 3 'zsh -c "source core/06_engine.zsh"'
π Debug TimingΒΆ
# Enable debug mode with per-file timing
ZSHAND_DEBUG=1 ZSHAND_TIMING_DETAIL=1 exec zsh
# Output shows per-file and per-directory timings:
# β± shared_functions: 3.2ms (18 files)
# β± core: 18.7ms (11 files)
# β core/06_engine.zsh: 8.4ms β οΈ OVER BUDGET
# β± hooks: 8.9ms (9 files)
π CI Regression DetectionΒΆ
# In CI pipeline:
hyperfine --warmup 3 --min-runs 10 'zsh -ic exit' --export-json current.json
# Compare against baseline (fail if >10ms regression)
# (custom script or manual comparison)
π Common BottlenecksΒΆ
| Bottleneck | Typical Cost | Fix |
|---|---|---|
| π Plugin loading (oh-my-zsh) | 30β100ms | Bundle plugins, use ZSHAND_AUTO_FULL |
| πͺ¨ mise/nvm/pyenv activation | 10β30ms | Cache activation output |
| π Atuin daemon start | 5β20ms | Background start with readiness loop |
| π Completion generation | 10β50ms | Cache completions, generate lazily |
| π Glob expansion (many files) | 5β15ms | Reduce glob scope, use (N) nullglob |
| π Command existence checks | 1β3ms each | Batch checks, cache results |
| π Network checks | 50β500ms | Never in startup β cache with TTL |
π Continuous MonitoringΒΆ
π Regular ChecksΒΆ
# Weekly: Full profile to catch regressions
zprofile --full
# After changes: Quick benchmark
hyperfine --warmup 3 'zsh -ic exit'
# After adding hooks/plugins: Check hook timing
ZSHAND_DEBUG=1 exec zsh
β οΈ Warning SignsΒΆ
- π΄ Startup >100ms β Profile immediately
- π΄ Single file >50ms β Investigate and optimize
- π‘ New hook >10ms β Consider deferring
- π‘ Grade dropped β Compare with previous profile
Next: Profile Report Guide (how to read the report) Β· Architecture (loading strategies) Β· Build System (zprofile, compile commands)
Next: Profile Report Guide (how to read the report) Β· Architecture (loading strategies) Β· Build System (zprofile, compile commands)
π Related DocumentsΒΆ
| Document | Purpose |
|---|---|
| π Profile Report Guide | How to read the graded profile output in detail |
| ποΈ Architecture | Bundle system and loading strategies |
| β‘ Auto-Full Mode | Fast path with a single compiled bundle |
| ποΈ Build System | Compilation commands and profiling bundle |
| π Header Standard | PERFORMANCE section in source headers |
| π Documentation Index | Full doc nav |