zsh Startup Benchmark

zsh Startup Benchmark

For a while now, I have been annoyed by how sluggish my ZSH shell feels when opening. Today I finally sat down at my computer and tried to speed things up a little. But what exactly should I speed up?

After a little research, I found out that ZSH has a built-in profiler. You can enable it by adding the following as the first line of ~/.zshrc:

zmodload zsh/zprof

After that, all commands and their execution times are recorded in the background when a new shell starts. You can get a report by running the zprof command. It is best to start a new shell and exit it again immediately. You can also measure the total execution time using the handy time command.

time zsh -i -c "zprof && exit"

The output from my shell now looked like this.

num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    1         120,03   120,03   97,98%     52,17    52,17   42,59%  zgen-init
 2)    2          27,64    13,82   22,56%     27,64    13,82   22,56%  compinit
 3)    1          14,58    14,58   11,90%     14,58    14,58   11,90%  handle_completion_insecurities
 4)    1           7,56     7,56    6,17%      7,56     7,56    6,17%  _zsh_highlight_bind_widgets
 5)    1           6,58     6,58    5,37%      6,45     6,45    5,26%  _zsh_highlight_load_highlighters
 6)    3           2,97     0,99    2,43%      2,97     0,99    2,43%  colors
 7)    4           1,75     0,44    1,43%      1,75     0,44    1,43%  _update_prompt
 8)    1           1,77     1,77    1,44%      1,74     1,74    1,42%  powerlevel9k_vcs_init
 9)    1           1,25     1,25    1,02%      1,25     1,25    1,02%  detect-clipboard
10)   36           1,44     0,04    1,18%      1,19     0,03    0,97%  set_default
11)    1           1,10     1,10    0,89%      1,10     1,10    0,89%  termColors
12)   10           1,02     0,10    0,83%      1,02     0,10    0,83%  compdef
13)    7           1,00     0,14    0,82%      1,00     0,14    0,82%  add-zsh-hook
14)    1           5,10     5,10    4,16%      0,77     0,77    0,63%  prompt_powerlevel9k_setup
15)    3           0,62     0,21    0,51%      0,62     0,21    0,51%  is-at-least
16)   41           0,31     0,01    0,25%      0,31     0,01    0,25%  defined
17)    1           0,18     0,18    0,15%      0,18     0,18    0,15%  (anon)
18)    2           0,06     0,03    0,05%      0,06     0,03    0,05%  segment_in_use
19)    1           0,08     0,08    0,07%      0,04     0,04    0,04%  print_deprecation_warning
20)    2           0,04     0,02    0,03%      0,04     0,02    0,03%  env_default
21)    1           0,04     0,04    0,04%      0,03     0,03    0,03%  zgen
22)    1           0,02     0,02    0,02%      0,02     0,02    0,02%  bashcompinit
23)    1           0,01     0,01    0,01%      0,01     0,01    0,01%  zgen-saved

As you can see, a large part of the time is spent on zgen init.

Do not forget to disable the profiler in the .zshrc file again after the benchmark!

Switching from zgen to zplug

During the performance analysis, I switched my ZSH plugins from zgen to zplug, which is supposed to be considerably faster. It seems to have made quite a difference for me as well. Startup speed improved by around 20%. There is a short migration guide here: https://github.com/zplug/zplug/wiki/Migration

My logic for loading zplug plugins now looks like this:

# Check if zplug is installed
if [[ ! -d ~/.zplug ]]; then
  git clone https://github.com/zplug/zplug ~/.zplug
  source ~/.zplug/init.zsh && zplug update --self
fi

source "${HOME}/.zplug/init.zsh"

# Make sure to use double quotes to prevent shell expansion
zplug "plugins/command-not-found", from:oh-my-zsh
zplug "plugins/git", from:oh-my-zsh
zplug "plugins/sudo", from:oh-my-zsh
zplug "plugins/ssh", from:oh-my-zsh
zplug "plugins/z", from:oh-my-zsh
zplug "plugins/shrink-path", from:oh-my-zsh
zplug "bhilburn/powerlevel9k", use:powerlevel9k.zsh-theme

zplug "zsh-users/zsh-completions"
zplug "zsh-users/zsh-autosuggestions"
zplug "zsh-users/zsh-syntax-highlighting", defer:2
zplug "zsh-users/zsh-history-substring-search", defer:3

zplug "junegunn/fzf", use:"shell *.zsh"
zplug "junegunn/fzf", as:command, hook-build:"./install --bin", use:"bin/{fzf-tmux,fzf}"

# Install packages that have not been installed yet
if ! zplug check; then
    printf "Install? \[y/N\]: "
    if read -q; then
        echo; zplug install
    else
        echo
    fi
fi

zplug load

During the analysis, I also removed a few things I did not really need. Now my shell starts lightning fast again.

time zsh -i -c "exit"
zsh -i -c "exit"  0,19s user 0,09s system 113% cpu 0,244 total