Den Start der Zsh beschleunigen

Die Konfigurationsdateien meiner Zsh wurden über die Jahre immer größer und umfangreicher. Ich habe jetzt schon einiges rausgeworfen, aber der Start der Shell ist immer noch langsam. Sehr langsam:

$ time zsh -i -c exit
zsh -i -c exit  1.22s user 1.05s system 98% cpu 2.314 total

Die einfachste, aber auch aufwendigste Form der Fehlersuche ist, alles zu kommentieren, eine Shell zu starten, zu prüfen ob sich was geändert hat und dann nach und nach weitere Abschnitte der Konfigurationsdatei hinzuzufügen und das ganze zu wiederholen. Alternativ dazu kann man auch Modul zsh/zprof laden und damit auf Fehlersuche gehen. Der Vorgang ist denkbar einfach. Man fügt zmodload zsh/zprof an den Anfang der ~/.zshrc und zprof ans Ende hinzu; wenn man anschließend eine neue Shell startet, bekommt man (also in dem Fall ich) folgende Ausgabe zu sehen:

num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    2        1062.07   531.03   60.31%    517.73   258.86   29.40%  nvm_auto
 2)    4         521.46   130.36   29.61%    277.64    69.41   15.77%  nvm
 3)    2         263.98   131.99   14.99%    263.98   131.99   14.99%  compdump
 4) 1702         221.30     0.13   12.57%    221.30     0.13   12.57%  compdef
 5)    2         693.30   346.65   39.37%    179.38    89.69   10.19%  compinit
 6)    2         207.43   103.71   11.78%    179.06    89.53   10.17%  nvm_ensure_version_installed
 7)    2          36.27    18.14    2.06%     35.97    17.98    2.04%  nvm_die_on_prefix
 8)    4          28.83     7.21    1.64%     28.83     7.21    1.64%  compaudit
 9)    2          28.36    14.18    1.61%     28.36    14.18    1.61%  nvm_is_version_installed
10)    1          12.27    12.27    0.70%      8.46     8.46    0.48%  nvm_validate_implicit_alias
11)    1          22.88    22.88    1.30%      8.42     8.42    0.48%  nvm_is_valid_version
12)    1           3.75     3.75    0.21%      3.75     3.75    0.21%  nvm_echo
13)    1           2.19     2.19    0.12%      2.19     2.19    0.12%  nvm_version_greater_than_or_equal_to
14)    1           1.60     1.60    0.09%      1.60     1.60    0.09%  colors
15)   14           1.56     0.11    0.09%      1.56     0.11    0.09%  add-zsh-hook
16)    1           1.34     1.34    0.08%      1.34     1.34    0.08%  kitty-integration
17)    8           0.30     0.04    0.02%      0.30     0.04    0.02%  nvm_npmrc_bad_news_bears
18)    1           0.29     0.29    0.02%      0.29     0.29    0.02%  is-at-least
19)    1           0.19     0.19    0.01%      0.19     0.19    0.01%  (anon) [/home/dope/.zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh:458]
20)    1           0.14     0.14    0.01%      0.14     0.14    0.01%  _omp_cleanup
21)    2           0.12     0.06    0.01%      0.12     0.06    0.01%  nvm_has
22)    1           0.08     0.08    0.00%      0.08     0.08    0.00%  colorize_man
23)    1           0.76     0.76    0.04%      0.08     0.08    0.00%  enable_you_should_use
24)    1           0.15     0.15    0.01%      0.07     0.07    0.00%  complete
25)    1           0.41     0.41    0.02%      0.07     0.07    0.00%  disable_you_should_use
26)    2        1062.13   531.07   60.31%      0.06     0.03    0.00%  nvm_process_parameters
27)    1           3.80     3.80    0.22%      0.05     0.05    0.00%  nvm_err
28)    1           0.03     0.03    0.00%      0.03     0.03    0.00%  _omp_create_widget
29)    2           0.02     0.01    0.00%      0.02     0.01    0.00%  nvm_is_zsh
30)    1           0.02     0.02    0.00%      0.02     0.02    0.00%  bashcompinit
[ Die originale Ausgabe ist noch länger, aber nicht wirklich relevant ]

So.. mein erster Gedanke war "ich hab NVM installiert?!" und siehe da.. Ja. Habe ich.

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
if [ -f /usr/share/nvm/init-nvm.sh ]; then
    source /usr/share/nvm/init-nvm.sh
fi

Eine Option ist, den Müll einfach zu kommentieren und fertig, was meine Vorgehensweise war. Wer NVM (wieso auch immer) benötigt, kann einen Blick auf das Plugin zsh-nvm-lazy-load werfen.
Nachdem man den Start von NVM deaktiviert hat, sieht die Welt schon etwas besser aus:

$ time zsh -i -c exit
$ zsh -i -c exit  0.31s user 0.17s system 65% cpu 0.729 total

Die nächsten beide Kandidaten sind dann compaudit, compinit und comdef. Alles was bei der Zsh mit "comp" anfängt, lädt das Completionsystem aka zshcompsys(1) und muss - zumindest in meinem Fall - geladen werden. Nur wird in meinem Fall compaudit zweimal aufgerufen; so.. was macht compaudit? Use the source luke!

# https://github.com/zsh-users/zsh/blob/master/Completion/compaudit
[...]
# Audit the fpath to assure that it contains all the directories needed by
# the completion system, and that those directories are at least unlikely
# to contain dangerous files.  This is far from perfect, as the modes or
# ownership of files or directories might change between the time of the
# audit and the time the function is executed.

# This function is designed to be called from compinit, which assumes that
# it is in the same directory, i.e., it can be autoloaded from the initial
# fpath as compinit was.  Most local parameter names in this function must
# therefore be the same as those used in compinit.
[...]

und die wird dann am Ende von compinit aufgerufen. Mit anderen Wort "Brauch ich also auch *gnarf*".

Note

Wenn man zprof mit der Option --sourcetrace ausführt, wird jede Datei ausgegeben die abgearbeitet wird

Also weiter im Text.. kitty-integration brauchewill ich. add-zsh-hook wird u. a. von meinem Prompt genutzt. Das Modul is-at-least wird genutzt um zu prüfen ob "mindestens $ZSH_VERSION installiert" ist und das wird von zsh-autosuggestions genutzt. colorize_man fliegt raus, weil bat mein $MANPAGER ist, aber zsh-you-should-use bleibt drin (ist manchmal ganz hilfreich) und colors macht das ganze übersichtlicher (finde ich zumindest).
Wenn man compinit in seiner ~/.zshrc initiiert, wird bei jedem Aufruf der Zsh geprüft ob ~/.zcompdump neu erstellt werden muss. Abstellen kann man das relativ einfach mit folgenden Zeilen:

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

Die Option -C überspringt die Prüfung und den Aufruf von compaudit (mehr dazu in den Sourcen von compinit). Ein anschließender Start der Zsh ging danach schon um einiges schneller:

$ time zsh -i -c exit
$ zsh -i -c exit  0.26s user 0.12s system 61% cpu 0.619 total

was daran lag, das die Zsh bei weitem weniger Aufrufe abzuarbeiten hatte

num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    1          15.51    15.51   72.65%     15.51    15.51   72.65%  compinit
 2)    1           2.64     2.64   12.35%      2.64     2.64   12.35%  colors
 3)   14           1.43     0.10    6.71%      1.43     0.10    6.71%  add-zsh-hook
 4)    1           0.97     0.97    4.56%      0.97     0.97    4.56%  kitty-integration
 5)    1           0.24     0.24    1.10%      0.24     0.24    1.10%  is-at-least
 6)    1           0.18     0.18    0.82%      0.18     0.18    0.82%  (anon) [/home/dope/.zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh:458]
 7)    1           0.12     0.12    0.58%      0.12     0.12    0.58%  _omp_cleanup
 8)    1           0.10     0.10    0.46%      0.10     0.10    0.46%  compdef
 9)    1           0.75     0.75    3.52%      0.07     0.07    0.33%  enable_you_should_use
10)    1           0.37     0.37    1.74%      0.07     0.07    0.32%  disable_you_should_use
11)    1           0.03     0.03    0.13%      0.03     0.03    0.13%  _omp_create_widget

Das ganze könnte man jetzt noch weiter optimieren, in dem man z. B. auf das Prompt-Theme verzichtet und stattdessen PS1 mit "#" oder "$" setzt, aber das würde bei mir ausarten, denn ich will einen visuellen Hinweis im PS1 ob ich via SSH/Tmux/root|user arbeite, auf welchem Host ich eingeloggt bin und welches mein $PWD ist. Ist zwar mit den Boardmitteln der Zsh machbar, aber da kann ich auch gleich bei Oh My Posh bleiben.

For comments, please send me an email