If you’ve used multiple tools, languages, build environments, etc.. as a CLI (command line utility), then you’ve probably experienced slow load times for your shell environment at some point. A simple way to determine which CLI is the slowest is to measure startup time. After identifying the slowest CLI tool with a profile, lazy loading that tool to the environment is the simplest approach to try when attempting to improve load times for a shell environment. I will provide an example specific to lazy loading
zsh
, but the general idea can be applied to other environments.
0. Diagnosing the problem
time zsh -i -c exit
- When my shell starts feeling a little sluggish, I use
time
to get an accurate measure of what I’m feeling. - On
unix
systems with a shell, you’ll probably have some version of thetime
utility. To get the specific information for your specific shell, I recommend readingman time
. The utility is useful for measuring CLI program run times. - In our example, we are simply passing
zsh
totime
to see how long it takes for it to start and stop. - The
-i
flag forceszsh
to be in interactive mode. - The
-c
flag passes any commands tozsh
in interactive mode. Remember to use native tools or tools in yourPATH
. - Output:
Saving session...completed.
zsh -i -c exit 0.66s user 0.72s system 89% cpu 1.543 total
- At first the results don’t seem so bad, but we can get a more granular look at what is being loaded.
- In your
.zshrc
add the following:
# beginning of .zshrc
zmodload zsh/zprof
# contents of .zshrc must be in between
# end of .zshrc
zprof
- zmodload loads the built in
zsh
modules on launch if added to the.zshrc
as above. - The zsh/zprof module profiles everything that is loaded on start in
zsh
. - On startup,
zsh
will output something similar to the output below. Once the source of the slow load time is identified, I usually comment outzmodload zsh/zprof
andzprof
in my.zshrc
for future use. - Output:
num calls time self name
-----------------------------------------------------------------------------------
1) 1 835.86 835.86 80.69% 309.23 309.23 29.85% nvm_auto
2) 2 480.08 240.04 46.35% 278.23 139.12 26.86% nvm
3) 1 170.17 170.17 16.43% 145.25 145.25 14.02% nvm_ensure_version_installed
4) 26 104.45 4.02 10.08% 78.22 3.01 7.55% _omz_source
5) 4 58.36 14.59 5.63% 58.36 14.59 5.63% compaudit
6) 1 31.50 31.50 3.04% 31.29 31.29 3.02% nvm_die_on_prefix
7) 2 87.03 43.51 8.40% 28.66 14.33 2.77% compinit
8) 1 46.55 46.55 4.49% 25.39 25.39 2.45% nvm_is_valid_version
9) 1 24.92 24.92 2.41% 24.92 24.92 2.41% nvm_is_version_installed
10) 1 15.93 15.93 1.54% 11.14 11.14 1.08% nvm_validate_implicit_alias
11) 1 10.13 10.13 0.98% 9.98 9.98 0.96% _zsh_highlight_load_highlighters
12) 1 7.96 7.96 0.77% 7.96 7.96 0.77% zrecompile
13) 1 5.81 5.81 0.56% 5.81 5.81 0.56% test-ls-args
14) 1 5.23 5.23 0.51% 5.23 5.23 0.51% nvm_version_greater_than_or_equal_to
15) 1 4.72 4.72 0.46% 4.72 4.72 0.46% nvm_echo
16) 1 2.12 2.12 0.21% 2.10 2.10 0.20% _zsh_highlight__function_callable_p
17) 1 1.67 1.67 0.16% 1.67 1.67 0.16% regexp-replace
18) 3 1.48 0.49 0.14% 1.40 0.47 0.14% add-zle-hook-widget
19) 9 1.37 0.15 0.13% 1.37 0.15 0.13% is-at-least
20) 13 1.35 0.10 0.13% 1.35 0.10 0.13% compdef
21) 1 1.16 1.16 0.11% 1.16 1.16 0.11% colors
22) 7 0.92 0.13 0.09% 0.92 0.13 0.09% add-zsh-hook
23) 1 0.27 0.27 0.03% 0.27 0.27 0.03% (anon) [/Users/ankit/.oh-my-zsh/custom/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh:458]
24) 4 0.21 0.05 0.02% 0.21 0.05 0.02% nvm_npmrc_bad_news_bears
25) 6 0.18 0.03 0.02% 0.18 0.03 0.02% is_plugin
26) 1 0.17 0.17 0.02% 0.17 0.17 0.02% nvm_has
27) 1 0.13 0.13 0.01% 0.13 0.13 0.01% zvm_exist_command
28) 1 0.22 0.22 0.02% 0.10 0.10 0.01% complete
29) 3 0.09 0.03 0.01% 0.09 0.03 0.01% is_theme
30) 1 0.08 0.08 0.01% 0.08 0.08 0.01% (anon) [/usr/local/Cellar/zsh/5.9/share/zsh/functions/add-zle-hook-widget:28]
31) 2 0.07 0.04 0.01% 0.07 0.04 0.01% env_default
32) 1 4.79 4.79 0.46% 0.07 0.07 0.01% nvm_err
33) 2 0.07 0.03 0.01% 0.07 0.03 0.01% bashcompinit
34) 1 835.90 835.90 80.69% 0.04 0.04 0.00% nvm_process_parameters
35) 1 0.02 0.02 0.00% 0.02 0.02 0.00% _zsh_highlight__is_function_p
36) 1 0.01 0.01 0.00% 0.01 0.01 0.00% nvm_is_zsh
37) 1 0.00 0.00 0.00% 0.00 0.00 0.00% _zsh_highlight_bind_widgets
- As a reminder, whenever
.zshrc
is changed. Saving then reloading it will ensure all changes are reflected. Reload withsource .PATH/TO/.zshrc
.
1. Lazy Load
- From the output of
zmodload zsh/zprof
, it seemsnvm
is the likely reason my start up time is slow. If I lazy load the right things, I could improve myzsh
start up time. - Lazy loading depends on your environment. If your local configuration is slightly different, then focusing on where your tools are loaded from is a reasonable starting point. Delaying the load of the slowest tool is the goal.
- To lazy load
nvm
, I added wrapper functions around thenvm
,node
,npm
, andnpx
variable names to change their respective scope (a.k.a overloading variable name).zsh
will see the function before something further down thePATH
chain. Whennvm
,node
,npm
, ornpx
is explicitly called thenzsh
will load the resources into the current session. - For me, adding the below functions to my
.zshrc
fixed myzsh
load time.
# lazy lode nvm instead of through oh-my-zsh to reduce load by 50%
lazy-nvm()
{
unset -f nvm node npm npx
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
}
nvm()
{
lazy-nvm
nvm $@
}
node()
{
lazy-nvm
node $@
}
npm()
{
lazy-nvm
npm $@
}
npx()
{
lazy-nvm
npx $@
}
2. Verification
time zsh -i -c exit
- After lazy loading
nvm
, myzsh
startup time was cut in half.
Saving session...completed.
zsh -i -c exit 0.32s user 0.31s system 92% cpu 0.673 total
- Adding the below lines to the
.zshrc
again will confirmnvm
isn’t loaded on start:
# beginning of .zshrc
zmodload zsh/zprof
# contents of .zshrc must be in between
# end of .zshrc
zprof
- Output:
num calls time self name
-----------------------------------------------------------------------------------
1) 26 109.34 4.21 71.45% 82.96 3.19 54.22% _omz_source
2) 2 29.39 14.70 19.21% 29.39 14.70 19.21% compaudit
3) 1 10.67 10.67 6.97% 10.51 10.51 6.87% _zsh_highlight_load_highlighters
4) 1 36.67 36.67 23.96% 7.27 7.27 4.75% compinit
5) 1 6.67 6.67 4.36% 6.67 6.67 4.36% zrecompile
6) 1 5.15 5.15 3.36% 5.15 5.15 3.36% test-ls-args
7) 1 2.21 2.21 1.45% 2.19 2.19 1.43% _zsh_highlight__function_callable_p
8) 3 1.74 0.58 1.14% 1.63 0.54 1.07% add-zle-hook-widget
9) 9 1.56 0.17 1.02% 1.56 0.17 1.02% is-at-least
10) 12 1.50 0.12 0.98% 1.50 0.12 0.98% compdef
11) 1 1.20 1.20 0.78% 1.20 1.20 0.78% colors
12) 7 1.07 0.15 0.70% 1.07 0.15 0.70% add-zsh-hook
13) 1 0.88 0.88 0.57% 0.88 0.88 0.57% regexp-replace
14) 1 0.26 0.26 0.17% 0.26 0.26 0.17% (anon) [/Users/ankit/.oh-my-zsh/custom/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh:458]
15) 6 0.26 0.04 0.17% 0.26 0.04 0.17% is_plugin
16) 1 0.19 0.19 0.13% 0.19 0.19 0.13% zvm_exist_command
17) 1 0.11 0.11 0.07% 0.11 0.11 0.07% (anon) [/usr/local/Cellar/zsh/5.9/share/zsh/functions/add-zle-hook-widget:28]
18) 3 0.09 0.03 0.06% 0.09 0.03 0.06% is_theme
19) 2 0.08 0.04 0.05% 0.08 0.04 0.05% env_default
20) 1 0.02 0.02 0.02% 0.02 0.02 0.02% bashcompinit
21) 1 0.02 0.02 0.01% 0.02 0.02 0.01% _zsh_highlight__is_function_p
22) 1 0.00 0.00 0.00% 0.00 0.00 0.00% _zsh_highlight_bind_widgets
-
As a reminder, whenever
.zshrc
is changed. Saving then reloading it will ensure all changes are reflected. Reload withsource .PATH/TO/.zshrc
. -
So if we run the below command:
time zsh -i -c exit && time zsh -i -c "nvm --version" exit
- The output will show an increased load time with
nvm
. bash
orzsh
will read the commands left to righttime zsh -i -c exit
will complete beforetime zsh -i -c "nvm --version" exit
.
Saving session...completed.
zsh -i -c exit 0.32s user 0.30s system 92% cpu 0.670 total
Restored session: Mon Feb 17 21:34:18 CST 2025
0.40.1
Saving session...completed.
zsh -i -c "nvm --version" exit 0.73s user 0.90s system 99% cpu 1.641 total
time zsh -i -c "nvm --version" exit | time zsh -i -c exit | |
---|---|---|
User | 0.73s | 0.32s |
System | 0.90s | 0.30s |
Total | 1.641 | 0.670 |