danielmoch.com

Static site for www.danielmoch.com
git clone git://git.danielmoch.com/danielmoch.com.git
Log | Files | Refs | LICENSE

zsh-compinit-rtfm.md (5645B)


      1 title: Zsh Compinit ... RTFM
      2 author: Daniel Moch
      3 copyright: 2018, Daniel Moch
      4 date: 2018-11-09 06:04:08 UTC-05:00
      5 category: technology
      6 description: Slow start times in Zsh are probably a sign of poor configuration
      7 
      8 This week I dealt with a problem that had been bugging me. I noticed
      9 that the time a took to start a new [Zsh](https://www.zsh.org)
     10 terminal session went from essentially instant to around 4
     11 seconds[^it]. I take some pride in running a lightweight system, so
     12 the thought of having to wait a few seconds for my terminal emulator
     13 to display a prompt feels like a personal affront. My system wasn't
     14 just behaving badly, it was challenging me by way of insult.
     15 
     16 Accepting the challenge laid before me, I took to [my favorite search
     17 engine](https://duckduckgo.com) to see what tools were available to help me understand what
     18 was suddenly performing so poorly. Oh, okay. [This
     19 post](https://xebia.com/blog/profiling-zsh-shell-scripts/) says that
     20 Zsh includes a script profiler. All I need to do is turn it on in my
     21 ``.zshrc`` file, like so:
     22 
     23 		zmodload zsh/zprof
     24 
     25 Maybe this challenge won't be so challenging after all. So I restart my
     26 shell and run the ``zprof`` command, and I see this::
     27 
     28 		num  calls                time                       self            name
     29 		-----------------------------------------------------------------------------------
     30 		 1)    1         203.91   203.91   45.30%    203.91   203.91   45.30%  compdump
     31 		 2)  744         109.84     0.15   24.40%    109.84     0.15   24.40%  compdef
     32 		 3)    1         448.41   448.41   99.62%    108.01   108.01   24.00%  compinit
     33 		 4)    2          26.75    13.38    5.94%     26.75    13.38    5.94%  compaudit
     34 		 5)    1           1.37     1.37    0.30%      1.37     1.37    0.30%  colors
     35 		 6)    2           0.12     0.06    0.03%      0.12     0.06    0.03%  set_title
     36 		 7)    1           0.15     0.15    0.03%      0.08     0.08    0.02%  preexec
     37 		 8)    1           0.07     0.07    0.02%      0.03     0.03    0.01%  precmd
     38 
     39 Okay, so initializing the completion system (i.e., all of the items with
     40 names beginning with "comp") is taking a combined 99.64% of the startup
     41 time. No need to do any fancy pareto analysis here. Something is getting
     42 borked initializing Zsh's much-vaunted completion system.
     43 
     44 So I [DuckDuckWent](https://duckduckgo.com)[^jk] and found some
     45 results that purported to fix the issue, except that they really only
     46 half fixed it for me (literally cutting my start time in half ... an
     47 improvement for sure, but not good enough). All of the advice[^ad]
     48 seemed to point in the same direction, which was basically:
     49 
     50 		autoload -Uz compinit
     51 		if [ $(date +'%j') != $(stat -f '%Sm' -t '%j' ~/.zcompdump) ]; then
     52 		  compinit
     53 		else
     54 		  compinit -C
     55 		fi
     56 
     57 The effect of which is to only check ``.zcompdump`` when it's more than
     58 24 hours old, and otherwise to simply initialize the completion system
     59 without referring to it. But wait, why should this be necessary? The
     60 whole point of ``.zcompdump`` is to speed up compinit. If ignoring it
     61 becomes the fix for slow compinit, then why should I ever use it?
     62 
     63 So I read the effing manual, and indeed my instincts were correct. To
     64 quote:
     65 
     66 > To speed up the running of compinit, it can be made to produce a
     67 > dumped configuration that will be read in on future invocations. ...
     68 > The dumped file is .zcompdump ...
     69 
     70 But why is it not working the way the manual describes? And why do so
     71 many other people seem to see similar behavior to me? Let's read a
     72 little further.
     73 
     74 > If the number of completion files changes, compinit will recognise
     75 > this and produce a new dump file.
     76 
     77 Aha! So by implication one needs to be careful how they go about
     78 initializing the completion system. If you do something stupid like,
     79 say, eval a completion script (effectively initializing the completion
     80 system) before you update your fpath[^fp] and otherwise run
     81 ``compinit``, then the number of completion files Zsh sees will differ
     82 by one between when you eval the completion script and when you later
     83 call ``compinit``, meaning you'll fully run ``compdump`` every time you
     84 source your ``.zshrc`` ... twice.
     85 
     86 Once I understood this, the fix was obvious. Take the completion script
     87 I was manually eval-ing, turn it into a function script, and add it to
     88 my ``fpath`` before running ``compinit`` so it gets picked up with the
     89 rest of the completion system initialization. Now the ``.zcompdump``
     90 isn't updated every time a spawn a new shell, and the initialization
     91 time dropped down to 60-90 ms for an improvement of over 90% in the
     92 worst case.
     93 
     94 Sidebar: Oh My Zsh
     95 ------------------
     96 
     97 [Oh My Zsh](https://ohmyz.sh/) seems by all accounts to be a very
     98 popular framework for managing your Zsh configuration, but while I
     99 don't personally use it, my impression is that it gives you enough
    100 rope to hang yourself with in this respect. I could see how it would
    101 be easy were I to source a bunch of third-party helper scripts in my
    102 ``.zshrc`` that were written by a half-dozen different people for the
    103 completion system initialization to end up in a similar state. Just
    104 something to keep in mind if the themes, plugins, and other candy
    105 offered by Oh My Zsh is too much for you to pass up.
    106 
    107 [^it]: Later measurement indicated the actual time was 1.0-1.5s, but either way it felt like forever.
    108 
    109 [^jk]: https://octodon.social/@jordyd/100189663891719203
    110 
    111 [^ad]: Probably the best written example I found was at https://carlosbecker.com/posts/speeding-up-zsh/, from which I pulled the above quote.
    112 
    113 [^fp]: ``fpath`` is the Zsh function path, which is similar to your system path, but for sourcing function scripts rather than executables