Skip to content

⚑ 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):

ZSHAND_PROFILE_BUNDLE=1 exec zsh

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):

hyperfine --warmup 3 'zsh -ic exit'

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:

# Enable lazy loading
export ZSHAND_LAZY_LOAD=1

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/wget during init
  • 🚫 Never ping synchronously (cache the result)
  • 🚫 Never git fetch during 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ΒΆ

# 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)


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