I am a heavy tmux
and zsh
user and today I noticed that starting a new tmux pane started getting slow. Sometimes it took more than 1 second for my shell in the new pane to become interactive.
The only thing a new pane is doing is starting up zsh
, so zsh initialization was the likely culprit.
As with any optimization, before we start making changes we want to measure the current performance. The following command shows us how long it takes for zsh to startup and become interactive.
$ time zsh -i -c exit
zsh -i -c exit 0.67s user 0.06s system 86% cpu 0.842 total
Zsh has a builtin profiler to profile startup time usage. It is called zprof
and can be enabled by adding zmodload zsh/zprof
to the top and zprof
to the bottom of your ~/.zshrc
.
Now when we start up zsh, it will start profiling, execute the rest of ~/.zshrc
and print out the results:
$ zsh
num calls time self name
-----------------------------------------------------------------------------------
1) 1 305.98 305.98 50.52% 305.95 305.95 50.51% nvm_die_on_prefix
2) 1 463.42 463.42 76.51% 82.96 82.96 13.70% nvm
3) 1 79.69 79.69 13.16% 79.53 79.53 13.13% __kubectl_bash_source
4) 1 74.43 74.43 12.29% 74.43 74.43 12.29% nvm_ensure_version_installed
5) 3 30.63 10.21 5.06% 30.63 10.21 5.06% compaudit
6) 1 12.47 12.47 2.06% 12.32 12.32 2.03% __kops_bash_source
7) 1 20.58 20.58 3.40% 7.16 7.16 1.18% compinit
8) 2 3.33 1.66 0.55% 3.33 1.66 0.55% env_default
9) 2 3.24 1.62 0.53% 3.24 1.62 0.53% grep-flag-available
10) 1 465.72 465.72 76.89% 2.30 2.30 0.38% nvm_auto
11) 1 1.31 1.31 0.22% 1.31 1.31 0.22% colors
12) 18 1.03 0.06 0.17% 1.03 0.06 0.17% compdef
13) 1 0.73 0.73 0.12% 0.73 0.73 0.12% nvm_supports_source_options
14) 1 0.27 0.27 0.04% 0.27 0.27 0.04% is-at-least
15) 2 0.31 0.16 0.05% 0.16 0.08 0.03% complete
16) 4 0.11 0.03 0.02% 0.11 0.03 0.02% is_plugin
17) 3 0.11 0.04 0.02% 0.11 0.04 0.02% nvm_has
18) 1 0.06 0.06 0.01% 0.06 0.06 0.01% start_tmux
19) 2 0.05 0.03 0.01% 0.05 0.03 0.01% bashcompinit
20) 1 466.49 466.49 77.02% 0.04 0.04 0.01% nvm_process_parameters
...
These results show that almost 90% of startup time is taken by nvm
(the Nodejs version manager) and bash completion initialization for kubectl
and kops
(2 kubernetes tools).
I commented out the nvm
initialization as I don’t use Node anymore. I moved the Kubernetes bash completion initialization inside a kube()
function that was already there for configuration my kubernetes credentials. This way the bash completions only load lazily when I configure my shell for a specific kubernetes cluster.
function kube() {
if [ $commands[kubectl] ]; then
source <(kubectl completion zsh)
fi
if [ $commands[kops] ]; then
source <(kops completion --shell zsh)
fi
# omitted kubectl configuration code
}
After making the changes I ran zsh with the profiler again:
$ zsh
num calls time self name
-----------------------------------------------------------------------------------
1) 3 22.71 7.57 60.31% 22.71 7.57 60.31% compaudit
2) 1 17.40 17.40 46.20% 6.85 6.85 18.20% compinit
3) 2 3.14 1.57 8.34% 3.14 1.57 8.34% grep-flag-available
4) 2 2.73 1.36 7.24% 2.73 1.36 7.24% env_default
5) 1 0.96 0.96 2.55% 0.96 0.96 2.55% colors
6) 16 0.89 0.06 2.37% 0.89 0.06 2.37% compdef
7) 1 0.22 0.22 0.58% 0.22 0.22 0.58% is-at-least
8) 4 0.08 0.02 0.22% 0.08 0.02 0.22% is_plugin
9) 1 0.07 0.07 0.19% 0.07 0.07 0.19% start_tmux
And with profiling disabled to measure the result of our optimizations
$ time zsh -i -c exit
zsh -i -c exit 0.04s user 0.03s system 70% cpu 0.108 total
Now it starts up in 108ms instead of 842ms which is a noticeable improvement when opening new tmux panes.
The largest offenders now seem to be compaudit
and compinit
which need 22ms and 17ms to complete. I did a Google search to find solutions for improving these but I could not find anything that was worth the effort. For now +- 100ms startup time for Zsh is good enough
and I’m going to call it a day.