My startup time was around 400ms, mostly because of NVM. I was using bash, and switched over to zsh. I'm now lazy loading NVM using the oh-my-zsh plugin and now my startup time is around 100ms. Not very fast but an improvement nonetheless. Thanks!!
Thanks for writing post. This gave me curiousity to check my shell startup time. I found it takes ~1.2 seconds to load SHELL and after fixing nvm (culprit who was taking most of time) it has reduced to 0.121ms which is great 👍
Multiple (!) times loading compinit, several completions for CLI tools I'm not using anymore, auto updates. I'm still using oh my zsh, that's the main culprit now. Getting a new work machine next week, will be ditching oh my zsh then and using your last post as a guide!
Side note: Even any shell other than rc should be pretty damn fast. If it's not, because too much configuration junk has accumulated (example: do you REALLY need a coloured prompt?), then it probably doesn't matter which shell you use. A shell is not an IDE, so it shouldn't be set up as if it were ;-)
Sure, and I (sometimes) start Acme which is basically a tiled toolbar for shell commands, but the point I was trying to make is that the commands you run inside your shell are the relevant part. I mean, (almost?) everything you do on the shell works the same way in any other shell (give or take certain library features, like readline's tab completion, which is also an optional part of rc anyway). It makes no sense to have a "batteries-included" shell with a long startup time. The batteries are your external commands.
I really like having information about git status in my prompt (if I'm in a git repository), but as you say that can slow down "time to next prompt" to a noticeable degree (especially in large git repos). As an alternative to removing the information, however, I recommend that people look for async solutions. I'm currently using woefe/git-prompt.zsh, and it removes the drag from git information completely. (I've also seen solutions for zsh that use a daemon or a cache, but I haven't investigated those.)
With this test on my computer, bash takes 7 ms to start, or occasionally 6 ms. What is everybody doing that's taking 10x or 100x as long? That's bonkers.
Measuring with `hyperfine`, I get 13ms, with a "fancy" prompt. I wrote the prompt in Zig, which makes it easy to ensure that there are no allocations, no syscalls other than one `write()`, etc. I never measured it like this, but seems like it worked out quite well.
>Think about it this way: (1) which program do you execute more often than your shell? (2) How many shells do you spawn every day? (3) How many other programs do you run every day that spawn your shell?
You seem to be conflating interactive (1, 2) and non-interactive shell invocations (3), here. The reality is that interactive shell startup time (here 0.02s for zsh with all the usual completions) doesn't matter as long as it's not noticeable; for the non-interactive case (the one spawned by other programs at a rate great enough to make that latency important), zsh is basically as instantaneous as dash or busybox (which are barren due to focus on POSIX compliance).
I guess the morale simply is "don't use zsh frameworks for wannabe ricers".
Yup yup, good point! I didn't accidentally conflate this but rather brushed over it in the name of "make sure nothing runs that you don't want to run", because sometimes things end up in .zprofile, .zshenv, .zshrc etc.
My startup time was around 400ms, mostly because of NVM. I was using bash, and switched over to zsh. I'm now lazy loading NVM using the oh-my-zsh plugin and now my startup time is around 100ms. Not very fast but an improvement nonetheless. Thanks!!
https://github.com/Schniz/fnm solved my problems with nvm startup time, highly recommend to give it a go!
Thanks for writing post. This gave me curiousity to check my shell startup time. I found it takes ~1.2 seconds to load SHELL and after fixing nvm (culprit who was taking most of time) it has reduced to 0.121ms which is great 👍
Thank you once again.
➜ dotfiles git:(main) ✗ for i in $(seq 1 10); do time $SHELL -i -c exit; done
$SHELL -i -c exit 0.03s user 0.00s system 99% cpu 0.030 total
$SHELL -i -c exit 0.02s user 0.01s system 99% cpu 0.029 total
$SHELL -i -c exit 0.02s user 0.00s system 98% cpu 0.029 total
$SHELL -i -c exit 0.02s user 0.01s system 99% cpu 0.027 total
$SHELL -i -c exit 0.02s user 0.00s system 98% cpu 0.028 total
$SHELL -i -c exit 0.02s user 0.01s system 99% cpu 0.028 total
$SHELL -i -c exit 0.03s user 0.00s system 99% cpu 0.027 total
$SHELL -i -c exit 0.02s user 0.00s system 99% cpu 0.027 total
$SHELL -i -c exit 0.02s user 0.01s system 99% cpu 0.028 total
$SHELL -i -c exit 0.02s user 0.01s system 99% cpu 0.028 total
but it used to take ~500ms with nvm, then found about lazy-loading it on GH (linked blogs show similar solutions):
https://github.com/nvm-sh/nvm/issues/1242#issuecomment-346946356
I'll give the zsh comp optimizations a try and report back, thanks!
Wow, thanks for this! I got it down from 400ms to 120ms.
What was the biggest culprit?
Multiple (!) times loading compinit, several completions for CLI tools I'm not using anymore, auto updates. I'm still using oh my zsh, that's the main culprit now. Getting a new work machine next week, will be ditching oh my zsh then and using your last post as a guide!
Ah! Multiple compinits — I've had that too. And yes, several completions can get you :)
❯ time zsh -i -c exit
zsh -i -c exit 0.40s user 0.17s system 95% cpu 0.606 total
At least 400ms need to go :)
yep, working on it
I don't care how long the shell is starting (currently around 400ms) as long as I have immediatly the prompt to type.
What changed my approach is Z-Init (Zi) https://github.com/zdharma/zi#calling-compinit-without-turbo-mode which lazy load a lot of things. So I can keep the "fluff" and they just appear as I have already started to type my commands.
If you want to easily compare the timing of a command, use the multitime tool
https://tratt.net/laurie/src/multitime/
Here is what it looks like on my computer:
```
[cyril:~] % multitime -n 10 zsh -i -c exit
===> multitime results
1: zsh -i -c exit
Mean Std.Dev. Min Median Max
real 0.219 0.019 0.188 0.217 0.262
user 0.118 0.013 0.103 0.119 0.142
sys 0.062 0.011 0.047 0.064 0.082
[cyril:~] % multitime -n 10 bash -i -c exit
exit
exit
exit
exit
exit
exit
exit
exit
exit
exit
===> multitime results
1: bash -i -c exit
Mean Std.Dev. Min Median Max
real 0.209 0.008 0.203 0.206 0.232
user 0.176 0.007 0.167 0.176 0.188
sys 0.031 0.008 0.020 0.034 0.045
```
I won.
; time rc -i -c exit
0,00 real 0,00 user 0,00 sys
; time rc -i -c exit
0,00 real 0,00 user 0,00 sys
; time rc -i -c exit
0,00 real 0,00 user 0,00 sys
; time rc -i -c exit
0,00 real 0,00 user 0,00 sys
; time rc -i -c exit
0,00 real 0,00 user 0,00 sys
Side note: Even any shell other than rc should be pretty damn fast. If it's not, because too much configuration junk has accumulated (example: do you REALLY need a coloured prompt?), then it probably doesn't matter which shell you use. A shell is not an IDE, so it shouldn't be set up as if it were ;-)
Ehhh, I get where you're coming from, but: https://blog.sanctum.geek.nz/series/unix-as-ide/
It /can/ be part of an IDE :)
Sure, and I (sometimes) start Acme which is basically a tiled toolbar for shell commands, but the point I was trying to make is that the commands you run inside your shell are the relevant part. I mean, (almost?) everything you do on the shell works the same way in any other shell (give or take certain library features, like readline's tab completion, which is also an optional part of rc anyway). It makes no sense to have a "batteries-included" shell with a long startup time. The batteries are your external commands.
I really like having information about git status in my prompt (if I'm in a git repository), but as you say that can slow down "time to next prompt" to a noticeable degree (especially in large git repos). As an alternative to removing the information, however, I recommend that people look for async solutions. I'm currently using woefe/git-prompt.zsh, and it removes the drag from git information completely. (I've also seen solutions for zsh that use a daemon or a cache, but I haven't investigated those.)
https://github.com/woefe/git-prompt.zsh
Another good tool for timing how long it takes to get a next prompt is zsh-prompt-benchmark.
https://github.com/romkatv/zsh-prompt-benchmark
Thanks for those links! I still have git information in my prompt, because I need it, but I also think _just_ running git is not that bad.
With this test on my computer, bash takes 7 ms to start, or occasionally 6 ms. What is everybody doing that's taking 10x or 100x as long? That's bonkers.
Different tools with shell hooks: NVM, Docker, asdf, rbenv, ...
You are 10x faster than my macOS, room to improve for me
```
$ [~] $ time zsh -i -c exit
zsh -i -c exit 0.32s user 0.48s system 99% cpu 0.794 total
$ [~] $ time zsh -i -c exit
zsh -i -c exit 0.32s user 0.47s system 98% cpu 0.803 total
$ [~] $ time zsh -i -c exit
zsh -i -c exit 0.33s user 0.51s system 98% cpu 0.849 total
$ [~] $ time zsh -i -c exit
zsh -i -c exit 0.32s user 0.52s system 99% cpu 0.843 total
$ [~] $ time zsh -i -c exit
zsh -i -c exit 0.32s user 0.50s system 100% cpu 0.813 total
```
And ~~2x slower than my primary dev environment VM
```
divyendusingh@ubuntu:~/zoid/plv8ify$ time bash -i -c exit
exit
real 0m0.043s
user 0m0.003s
sys 0m0.008s
divyendusingh@ubuntu:~/zoid/plv8ify$ time bash -i -c exit
exit
real 0m0.041s
user 0m0.004s
sys 0m0.004s
divyendusingh@ubuntu:~/zoid/plv8ify$ time bash -i -c exit
exit
real 0m0.011s
user 0m0.004s
sys 0m0.003s
divyendusingh@ubuntu:~/zoid/plv8ify$ time bash -i -c exit
exit
real 0m0.010s
user 0m0.005s
sys 0m0.002s
divyendusingh@ubuntu:~/zoid/plv8ify$ time bash -i -c exit
exit
real 0m0.011s
user 0m0.004s
sys 0m0.003s
```
Probably why it didn't bother me as much! (not because I don't notice slowness in terminal, but other parts of my dev setup are slower!)
Weighted clown shoes is such a good analogy!
I use hyperfine as a fancier `for i in $(seq)` and a `PS4=` trick to profile times of everything the rc file is doing: https://github.com/WillForan/dotconf/tree/master/bin/profile-bashrc
Fancy! I like it!
Measuring with `hyperfine`, I get 13ms, with a "fancy" prompt. I wrote the prompt in Zig, which makes it easy to ensure that there are no allocations, no syscalls other than one `write()`, etc. I never measured it like this, but seems like it worked out quite well.
>Think about it this way: (1) which program do you execute more often than your shell? (2) How many shells do you spawn every day? (3) How many other programs do you run every day that spawn your shell?
You seem to be conflating interactive (1, 2) and non-interactive shell invocations (3), here. The reality is that interactive shell startup time (here 0.02s for zsh with all the usual completions) doesn't matter as long as it's not noticeable; for the non-interactive case (the one spawned by other programs at a rate great enough to make that latency important), zsh is basically as instantaneous as dash or busybox (which are barren due to focus on POSIX compliance).
I guess the morale simply is "don't use zsh frameworks for wannabe ricers".
Related, a small latency comparison I did some ages ago: https://git.sr.ht/~q3cpma/interp_overhead/tree/master/item/results.txt
Yup yup, good point! I didn't accidentally conflate this but rather brushed over it in the name of "make sure nothing runs that you don't want to run", because sometimes things end up in .zprofile, .zshenv, .zshrc etc.
zsh -i -c exit 0.26s user 0.21s system 20% cpu 2.323 total
zsh -i -c exit 0.12s user 0.07s system 90% cpu 0.207 total
zsh -i -c exit 0.12s user 0.07s system 90% cpu 0.210 total
zsh -i -c exit 0.12s user 0.07s system 91% cpu 0.209 total
zsh -i -c exit 0.12s user 0.07s system 90% cpu 0.205 total
Any idea what's taking the longest time?
Yeah, it was pkgx.sh... switching to asdf because it is *way* faster.
I am now down to:
$SHELL -i -c exit 0.02s user 0.02s system 85% cpu 0.053 total