Little Thing #2 — Speeding up ZSH

Danny Smith
3 min readAug 9, 2018

This is part of my Little Things series.

Three years ago, I switched my shell from bash to zsh. Since then, I’ve added a bunch of stuff to my dotfiles and I’ve been getting increasingly annoyed that my shell is slow to load.

Today, I opened a new tab in iterm and ended up waiting about 15 seconds before I had a usable prompt.

Time to fix that

First things first, I wanted to see what was actually going on when I opened a new shell. Running zsh with some flags prints the commands it’s running under the hood:

$(which zsh) -lxv

First off, it was obvious there was a lot going on. I use Oh My ZSH, so lots of it was coming from there and the plugins I have installed. The other obvious sticking point was nvm. Not only was there loads of nvm-looking stuff, but it clearly spent a long time executing this line in my zshrc

"$(brew --prefix nvm)/nvm.sh"

This is a good hint, but to be sure, I switched on profiling with zprof.

zmodload zsh/zprof # top of your .zshrc file# Your .zshrc stuffzprof # bottom of .zshrc
I forgot to take a screenshot of the output at the beginning, so this is mid-way through my optimisations 🙃

The results pretty much confirmed mysuspicionss.

  1. NVM-related stuff was taking ages.
  2. Something called compaudit and compinit were also taking ages.

Fixing NPM

First off, I had a chunk of code that switched to the correct node version when I changed directories. I got it from here, but it clearly slowing down my cd commands, as well as the shell loading. So I trashed that. 🚮

Second, I was calling this, to make nvm available as a command:

"$(brew --prefix nvm)/nvm.sh"

Not only is the nvm.sh script itself pretty slow, but brew command takes about a second. It turns out that NVM isn’t actually an executable — it’s just a shell function. You can see that if you run which nvm.

The obvious thing to do was create a nvm() function in .zshrc . If the script has never been run we should unset our nvm() function and run it, then cal; nvm with the arguments it was originally called with…

nvm() {
echo "🚨 NVM not loaded! Loading now..."
unset -f nvm
export NVM_PREFIX=$(brew --prefix nvm)
[ -s "$NVM_PREFIX/nvm.sh" ] && . "$NVM_PREFIX/nvm.sh" nvm "$@"
}

This worked, but it occured to me that I probably want NVM available when I call any node-based command (things like create-react-app).

TIME TO RUN FOR GOOGLE 🏃‍

This pretty much does what’s needed…

Sorted.

What else is slow then?

Turns out that compinit is ZSH checking the cached .zcompdump to see if it needs regenerating. The simplest fix is to only do that once a day. Thanks to this comment, we can achieve that pretty easily.

autoload -Uz compinitfor dump in ~/.zcompdump(N.mh+24); do
compinit
done
compinit -C

Anything Else?

I took the opportunity to remove a few aliases and functions I no longer need, as well as a few zsh plugins I don’t really use.

If you want to go a bit further, Carlos Alexandro Becker wrote a decent article on the subject:

--

--

Danny Smith

Helping companies become high-performance remote organisations. Currently working with @HeyOyster . Used to write code. Occasional blues musician.