Sometimes I noticed a new shell's startup being really slow. This annoyed me from time to time, so today I decided to try some optimizations.
Measure
I measured my startup time in a rather crude way. I'm loading my zsh configuration files like in the following snippet:
# Source all .zsh files in my dotfiles
for config_file ($HOME/.dotfiles/**/*.zsh); do
source $config_file
done
I relied on subjective analysis and changed it to the following code:
# Source all .zsh files in my dotfiles
for config_file ($HOME/.dotfiles/**/*.zsh); do
echo $config_file
source $config_file
read throwaway\?"Press return for next file"
done
What this does is print the path to the configuration file, load it and then wait for a return key press to load the next file.
With this, I could start a new shell and see which files are slow to load.
Remove brew --prefix
From the measurements I noticed that the slow files were those where I load some tools via homebrew. I load for example grc
and hub
that way. I used this approach for my PATH
as well with PHP and the Android SDK.
I then timed an execution of brew --prefix hub
and got the following result:
$ time brew --prefix hub
/usr/local/opt/hub
brew --prefix hub 0.12s user 0.03s system 98% cpu 0.145 total
It takes around 0.15 seconds to load the path prefix for hub
! If you do that a couple of times in your zsh configuration, it sums up to a lot of time.
I then changed all the occurences to the absolute path, as it's mostly just /usr/local/opt
and the package name. It's not as flexible anymore, but that's a small price for a faster shell startup time.
Change rbenv
initialization
The one other file that loaded slowly was my rbenv initialization. It calls the following snippet:
if which rbenv > /dev/null; then
eval "$(rbenv init -)"
fi
A bit of googling revealed the --no-rehash
option which prevents an expensive rehashing of ruby and rubygems executables when loading rbenv. You can always rehash later with rbenv rehash
.
So I measured it again, to be sure it helps.
$ { time ( eval "$(rbenv init -)" ) } 2>&1
( eval "$(rbenv init -)"; ) 0.20s user 0.06s system 99% cpu 0.262 total
$ { time ( eval "$(rbenv init - --no-rehash)" ) } 2>&1
( eval "$(rbenv init - --no-rehash)"; ) 0.03s user 0.03s system 105% cpu 0.060 total
As you can see, --no-rehash
sped it up quite a bit.
As a side-effect, I learned how to measure code blocks in zsh ;-)
Results
With these changes, I decreased my zsh startup time from 1.5s to 0.6s.
It's nice to see that two small changes can speed up an application I use every day.
For more information, you can see my full zsh configuration (along with my other dotfiles) on GitHub.