Reducing zsh startup time by 400%
Posted on Thu 08 October 2020 in Programming
As part of the nature of my work, I have to interact with a lot of systems and
tools. My .zshrc
file has slowly grown in order to support this multi-project
workflow. So, too, has the startup time for my shell.
Want to know how long it takes right now? Over a second.
$ time zsh -i -c exit
zsh -i -c exit 1.24s user 0.84s system 85% cpu 2.423 total
That's insane! I use tmux, so I have to pay this price every time a new window or pane is opened and it's frustrating to say the least.
It's best to measure first before cutting. I followed Steven Van Bael's post on
profiling zsh startup time
and added zmodload zsh/zprof
as the first line and zprof
as the last in
~/.zshrc
.
num calls time self name
-----------------------------------------------------------------------------------
1) 1 203.32 203.32 24.07% 203.22 203.22 24.05% nvm_die_on_prefix
2) 1 182.80 182.80 21.64% 182.80 182.80 21.64% virtualenvwrapper_run_hook
3) 2 432.98 216.49 51.25% 147.32 73.66 17.44% nvm
4) 1 85.92 85.92 10.17% 85.77 85.77 10.15% __kubectl_bash_source
5) 1 82.22 82.22 9.73% 72.98 72.98 8.64% nvm_ensure_version_installed
6) 1 472.63 472.63 55.94% 39.65 39.65 4.69% nvm_auto
7) 1 30.29 30.29 3.59% 30.29 30.29 3.59% handle_completion_insecurities
8) 1 200.14 200.14 23.69% 16.70 16.70 1.98% virtualenvwrapper_initialize
9) 1 15.86 15.86 1.88% 15.86 15.86 1.88% compinit
10) 1 10.79 10.79 1.28% 10.68 10.68 1.26% _zsh_highlight_load_highlighters
11) 1 9.24 9.24 1.09% 9.24 9.24 1.09% nvm_is_version_installed
12) 2 5.88 2.94 0.70% 5.88 2.94 0.70% grep-flag-available
13) 1 5.47 5.47 0.65% 5.47 5.47 0.65% _zsh_highlight_bind_widgets
14) 1 4.12 4.12 0.49% 4.12 4.12 0.49% nvm_supports_source_options
15) 1 3.22 3.22 0.38% 3.20 3.20 0.38% powerlevel9k_vcs_init
16) 1 2.17 2.17 0.26% 2.17 2.17 0.26% termColors
17) 2 1.84 0.92 0.22% 1.84 0.92 0.22% env_default
18) 2 1.53 0.76 0.18% 1.53 0.76 0.18% colors
19) 1 7.69 7.69 0.91% 1.31 1.31 0.16% prompt_powerlevel9k_setup
20) 36 1.14 0.03 0.14% 0.96 0.03 0.11% set_default
21) 5 0.78 0.16 0.09% 0.78 0.16 0.09% add-zsh-hook
22) 1 0.59 0.59 0.07% 0.59 0.59 0.07% virtualenvwrapper_setup_tab_completion
23) 2 0.47 0.23 0.06% 0.47 0.23 0.06% is-at-least
24) 2 0.45 0.22 0.05% 0.45 0.22 0.05% bashcompinit
25) 4 0.34 0.09 0.04% 0.34 0.09 0.04% compdef
26) 2 0.32 0.16 0.04% 0.32 0.16 0.04% (anon)
27) 3 0.50 0.17 0.06% 0.24 0.08 0.03% complete
28) 2 0.22 0.11 0.03% 0.22 0.11 0.03% nvm_has
29) 41 0.22 0.01 0.03% 0.22 0.01 0.03% defined
30) 1 476.81 476.81 56.44% 0.06 0.06 0.01% nvm_process_parameters
31) 1 0.08 0.08 0.01% 0.05 0.05 0.01% print_deprecation_warning
32) 1 0.05 0.05 0.01% 0.05 0.05 0.01% virtualenvwrapper_verify_workon_home
33) 2 0.05 0.02 0.01% 0.05 0.02 0.01% segment_in_use
34) 1 0.02 0.02 0.00% 0.02 0.02 0.00% nvm_is_zsh
...
The biggest culprit is nvm
, the node version manager, followed by kubectl
,
the CLI tool for kubernetes. I need both, but I don't need them all the time. I
can move their initializations to a function or alias and call them on an
as-needed basis.
# Allow kubectl tab-completion
alias k='kubectl'
source <(kubectl completion zsh)
complete -F __start_kubectl k
export NVM_DIR=~/.nvm
source $(brew --prefix nvm)/nvm.sh
becomes
function init_kubectl() {
# Allow kubectl tab-completion
alias k='kubectl'
source <(kubectl completion zsh)
complete -F __start_kubectl k
}
function init_nvm() {
export NVM_DIR=~/.nvm
# source $(brew --prefix nvm)/nvm.sh
source /usr/local/opt/nvm/nvm.sh
}
$ time zsh -i -c exit
zsh -i -c exit 0.29s user 0.15s system 83% cpu 0.524 total
This comes out to a 427% performance improvement, but in practical terms it means that the startup time is much less of a PITA. Not bad for 5 minutes of effort; this is truly the Pareto principle at work.
If you are interested in a more in-depth analysis of zsh performance, htr3n's post on a faster zsh goes much further than I did.