Zsh Startup Acceleration

Zsh is already the default shell of macOS Catalina. As we configure more and more plugins for zsh, the startup speed of zsh becomes slower and slower. This article will introduce how to measure and optimize the startup speed of Zsh.

Mesure

We can use the time command to view the startup time of Zsh in a coarse-grained manner

Before optimization:

1
2
3
4
$ time zsh -i -c exit
Saving session...completed.
Deleting expired sessions... 60 completed.
zsh -i -c exit 0.21s user 0.21s system 92% cpu 0.453 total

After optimization(with nvm):

1
2
3
$ time zsh -i -c exit             
Saving session...completed.
zsh -i -c exit 0.13s user 0.04s system 93% cpu 0.179 total

After optimization(without nvm):

1
2
Saving session...completed.
zsh -i -c exit 0.10s user 0.09s system 85% cpu 0.229 total

Troubleshoot

Use zprof to see the time-consuming of Zsh loading. Edit the ~/.zshrc file and add the following code at the beginning.

1
zmodload zsh/zprof

Start zsh again, and enter zprof , you can see that it lists specific time-consuming items.

1
2
3
4
5
6
$ zprof
num calls time self name
-----------------------------------------------------------------------------------
1) 1 18.46 18.46 19.53% 18.46 18.46 19.53% compinit
2) 12 13.75 1.15 14.55% 13.75 1.15 14.55% _zsh_highlight_main__type
3) 8 31.93 3.99 33.78% 7.69 0.96 8.13% _zsh_highlight

Optimization

use nvm

The nvm(Node Version Manager) is a well-known node.js version management tool. Take zsh as an example. After installing nvm, the following code is usually added to .zshrc.

1
2
3
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

But this item will seriously slow down the startup speed of zsh. We can change it to the following code.

1
2
3
4
5
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" --no-use # This loads nvm

alias node='unalias node ; unalias npm ; nvm use default ; node $@'
alias npm='unalias node ; unalias npm ; nvm use default ; npm $@'

But the solution above will ignore the packages which we install on system, for example, if you use npm install -g hexo, the hexo will not work until you use npm again manually.

So, it’s better to use the solution below, which provided from this link.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export NVM_DIR="$HOME/.nvm"
#[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
#[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

NODE_GLOBALS=(`find ~/.nvm/versions/node -maxdepth 3 -type l -wholename '*/bin/*' | xargs -n1 basename | sort | uniq`)
NODE_GLOBALS+=(node nvm yarn)

_load_nvm() {
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
}

for cmd in "${NODE_GLOBALS[@]}"; do
eval "function ${cmd}(){ unset -f ${NODE_GLOBALS[*]}; _load_nvm; unset -f _load_nvm; ${cmd} \$@; }"
done
unset cmd NODE_GLOBALS

export PATH="$PATH:$HOME/.yarn/bin"

use n

When we use the above solution, the commands installed through NPM cannot be used directly. So let’s use another Node version manager to replace NVM, which is n.

N is really easy to install, just use brew.

1
brew install n

And remove your NVM directories.

Put these lines on your .zshrc.

1
2
3
# For node.js
export N_PREFIX=$HOME/.n
export PATH="$PATH:$N_PREFIX/bin"

Reference(Thanks to these articles)

  1. nvm.sh is slow (200ms+)
  2. make oh my zsh fly