Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Speed up zsh compinit by only checking cache once a day.
# On slow systems, checking the cached .zcompdump file to see if it must be
# regenerated adds a noticable delay to zsh startup. This little hack restricts
# it to once a day. It should be pasted into your own completion file.
#
# The globbing is a little complicated here:
# - '#q' is an explicit glob qualifier that makes globbing work within zsh's [[ ]] construct.
# - 'N' makes the glob pattern evaluate to nothing when it doesn't match (rather than throw a globbing error)
# - '.' matches "regular files"
# - 'mh+24' matches files (or directories or whatever) that are older than 24 hours.
autoload -Uz compinit
if [[ -n ${ZDOTDIR}/.zcompdump(#qN.mh+24) ]]; then
compinit;
else
compinit -C;
fi;
@tardypad

This comment has been minimized.

Copy link

commented Nov 24, 2016

I think there would be a problem at the changing of the year since the day of the year %j would restart at 001.
I have added the year before in my config %Y%j, that should solve this "issue"
tardypad/dotfiles@aadaeaf

@ctechols

This comment has been minimized.

Copy link
Owner Author

commented Dec 21, 2016

You're absolutely right. I changed this to also use zsh globbing as the test. This eliminates two calls to the external 'date' command, and makes the test faster.

The optimization probably isn't noticeable on linux, but cygwin is crazy slow spawning a new process.

@ahmedre

This comment has been minimized.

Copy link

commented May 7, 2017

@ctechols thanks for writing this - i noticed on my machine that the if condition is always true, despite the fact that:

~
❯ ls .zsh/.zcompdump*(.mh-1)
.zsh/.zcompdump     .zsh/.zcompdump.zwc

❯ ls .zsh/.zcompdump*(.mh+24)
zsh: no matches found: .zsh/.zcompdump*(.mh+24)

i see mention of cN,m here, but can't get it to work (even just pulling the if statement out and running it, it always returns true, even with mM+24, for example, and even with mh-1, for example).

was curious if you had any ideas - thanks!

@ahmedre

This comment has been minimized.

Copy link

commented May 7, 2017

answering my own question - based on this, it seems that when there are no matches, the null glob lists all files (which explains why i always get this returning true). based on this, i changed my code to this (which is meh, but it works):

re_initialize=0
for match in $zcompdump*(.Nmh+24); do
   re_initialize=1
   break
done

if [ "$re_initialized" -eq "1" ]; then
   compinit
else
  # omit the check for new functions since we updated today
  compinit -C
fi
@codyhan94

This comment has been minimized.

Copy link

commented Jul 25, 2017

In addition to what @ahmedre said, you should also manually call compdump after compinit without the -C flag so that the date on your zcompdump file gets updated. From my experience, compinit won't update the compdump file if it hasn't changed, which means that we won't switch to running the fast compinit -C until we add or remove something to our completions.

re_initialize=0
for match in $zcompdump*(.Nmh+24); do
  re_initialize=1
  break
done

if [ "$re_initialized" -eq "1" ]; then
  compinit
  # update the timestamp on compdump file
  compdump
else
  # omit the check for new functions since we updated today
  compinit -C
fi
@voyeg3r

This comment has been minimized.

Copy link

commented Jul 30, 2017

For those who does not have $ZDOTDIR setted it will always give the same result, hence we have to add "${HOME}"
A second problem I stumbled upon is that oh-my-zsh defines a different name to .zcompudumpfile

This is a defined variable on oh-my-zsh
echo "$ZSH_COMPDUMP"

autoload -Uz compinit
if [[ -n ${ZDOTDIR:-${HOME}}/$ZSH_COMPDUMP(#qN.mh+24) ]]; then
	compinit -d $ZSH_COMPDUMP;
else
	compinit -C;
fi;

Look at this:
ls ${ZDOTDIR:-${HOME}}/.zcompdump
/home/sergio/.zcompdump
╭─sergio@arch-anywhere ~
╰─$ ls ${ZDOTDIR}/.zcompdump
ls: não foi possível acessar '/.zcompdump': File or dir not found
I only was able to figure out this because of the amazing "Silversearch" --> ag -l 'compinit' .

Some relevant information from zsh manpages:
# -D ........... disables .zcompudump file
# -d ........... used to set an alternative name to .zcompdump
# -i ........... accept insecure files
# -C ........... ignore checking at all
# To speed up the running of compinit, it can be made to produce a dumped
# configuration that will be read in on future invocations; this is the default,
# but can be turned off by calling compinit with the option -D. The dumped file
# is .zcompdump in the same directory as the startup files (i.e. $ZDOTDIR or
# $HOME); alternatively, an explicit file name can be given by ‘compinit -d
# dumpfile’. The next invocation of compinit will read the dumped file instead of
# performing a full initialization.
#
#  and to make compinit silently ignore all insecure files
#  and directories use the option -i  This  security  check
#  is  skipped entirely when the -C option is given.
@lsl

This comment has been minimized.

Copy link

commented Jan 1, 2018

@ahmedre / @codyhan94 you have a typo: re_initialized vs re_initialize

You can make the original code work - you just need to enable extended glob: setopt extendedglob

This should work fine without it:

autoload -Uz compinit

for dump in ~/.zcompdump(N.mh+24); do
  compinit
done

compinit -C
@danihodovic

This comment has been minimized.

Copy link

commented Jun 4, 2018

None of these solutions worked for me.

I did a bit of trial and error and the code below loads custom zsh-completions and is fast.

compinit -d ~/.zcompdump_custom

I'm not sure why this works. The only thing I added is the -d flag.

The zsh docs are abysmal.

@emilyst

This comment has been minimized.

Copy link

commented Nov 23, 2018

The comment last 1 Jan doesn't quite work, but it's close to the mark. Couple of improvements I've made.

  setopt EXTENDEDGLOB
  for dump in $HOME/.zcompdump(#qN.m1); do
    compinit
    if [[ -s "$dump" && (! -s "$dump.zwc" || "$dump" -nt "$dump.zwc") ]]; then
      zcompile "$dump"
    fi
  done
  unsetopt EXTENDEDGLOB
  compinit -C

First, I ensure explicitly that EXTENDEDGLOB is set, and I unset it after so that ^ will behave as normal after, among other things (so that you can, e.g., git reset HEAD^ without issue).

Then, to do the actual globbing based on the modification time, it's necessary to use #q (and avoid wrapping the expression in a string). This allows the glob qualifiers to work within a complex command. Without it, the glob qualifier seems not to work at all.

The implicit unit is a day, so using 24 hours seems redundant and needless. The implicit operator is a plus sign, so the mh+24 can be simplified to m1. The N is the NULL_GLOB qualifier, so it stays. The . is the regular file qualifier, so it stays (though it's probably unnecessary).

Finally, I throw in an extra step—compile the dump file if the completions dump has been updated. This ideally speeds up the use of the completions dump the next go 'round.

It's not strictly necessary to use a for-loop here, but it detects the existence of the file and defines a temporary, scoped variable to refer to that file in one line. If you decide to keep multiple dump files for some reason, you'll want to modify the compinit line to be less redundant.

@aksh1618

This comment has been minimized.

Copy link

commented Dec 17, 2018

Thanks to @voyeg3r and @emilyst, this works for me with oh-my-zsh:

# Speeds up load time
DISABLE_UPDATE_PROMPT=true

# Perform compinit only once a day.
autoload -Uz compinit

setopt EXTENDEDGLOB
for dump in $ZSH_COMPDUMP(#qN.m1); do
  compinit
  if [[ -s "$dump" && (! -s "$dump.zwc" || "$dump" -nt "$dump.zwc") ]]; then
    zcompile "$dump"
  fi
  echo "Initializing Completions..."
done
unsetopt EXTENDEDGLOB
compinit -C
@aksh1618

This comment has been minimized.

Copy link

commented Dec 29, 2018

Well, it seemed to work, but didn't actually. oh-my-zsh.sh calls compinit on its own, with a custom dumpfile. So for now, I have edited it as follows:

 72   # Else, enable and cache completions to the desired file.
 73   else
 74     compinit -C -d "${ZSH_COMPDUMP}"
 75   fi
 76 else
 77   compinit -C -d "${ZSH_COMPDUMP}"
 78 fi

and added this to zshrc:

find $HOME -maxdepth 1 -iname '.zcompdump*' -mtime 1 -delete | grep -q "." && source $HOME/.zshrc

This will make oh-my-zsh.sh recreate any compdump older than a day.

@gartrog

This comment has been minimized.

Copy link

commented Feb 13, 2019

@aksh1618 I believe it should be -mtime +1 to match files older than a day

@igorepst

This comment has been minimized.

Copy link

commented Apr 20, 2019

My version. Thanks to @emilyst and this snippet from prezto

_zpcompinit_custom() {
  setopt extendedglob local_options
  autoload -Uz compinit
  local zcd=${ZDOTDIR:-$HOME}/.zcompdump
  local zcdc="$zcd.zwc"
  # Compile the completion dump to increase startup speed, if dump is newer or doesn't exist,
  # in the background as this is doesn't affect the current session
  if [[ -f "$zcd"(#qN.m+1) ]]; then
        compinit -i -d "$zcd"
        { rm -f "$zcdc" && zcompile "$zcd" } &!
  else
        compinit -C -d "$zcd"
        { [[ ! -f "$zcdc" || "$zcd" -nt "$zcdc" ]] && rm -f "$zcdc" && zcompile "$zcd" } &!
  fi
}

Features:

  1. Implicit plus sign in glob didn't work for me, so I added it explicitly
  2. extendedglob is local inside the function
  3. zcompile is run in the background
  4. Somehow the compiled file changed his size pretty much (my config problem?), so I preferred to delete it explicitly

Usage

  1. Just call _zpcompinit_custom
  2. Or, if you use zplugin
  • Change zcd to local zcd=${ZPLGM[ZCOMPDUMP_PATH]:-${ZDOTDIR:-$HOME}/.zcompdump}
  • And call like this, according to the used plugin:
zplugin ice depth'1' lucid wait'0' atinit"_zpcompinit_custom; zpcdreplay"
zplugin light zdharma/fast-syntax-highlighting
@goldylucks

This comment has been minimized.

Copy link

commented Jul 24, 2019

It should be pasted into your own completion file.

@ctechhols is there a way to load it in once place?

I have dozens of those:

echo $fpath
/usr/lib/heroku/node_modules/@heroku-cli/plugin-autocomplete/autocomplete/zsh /home/linuxbrew/.linuxbrew/share/zsh/site-functions /home/goldy/.oh-my-zsh/plugins/heroku /home/goldy/.oh-my-zsh/plugins/colorize /home/goldy/.oh-my-zsh/plugins/colored-man-pages /home/goldy/.oh-my-zsh/plugins/git-extras /home/goldy/.oh-my-zsh/plugins/autojump /home/goldy/.oh-my-zsh/plugins/git /home/goldy/Dropbox/Mackup/custom-oh-my-zsh/plugins/yarn-autocompletions /home/goldy/.oh-my-zsh/functions /home/goldy/.oh-my-zsh/completions /usr/local/share/zsh/site-functions /usr/share/zsh/vendor-functions /usr/share/zsh/vendor-completions /usr/share/zsh/functions/Calendar /usr/share/zsh/functions/Chpwd /usr/share/zsh/functions/Completion /usr/share/zsh/functions/Completion/AIX /usr/share/zsh/functions/Completion/BSD /usr/share/zsh/functions/Completion/Base /usr/share/zsh/functions/Completion/Cygwin /usr/share/zsh/functions/Completion/Darwin /usr/share/zsh/functions/Completion/Debian /usr/share/zsh/functions/Completion/Linux /usr/share/zsh/functions/Completion/Mandriva /usr/share/zsh/functions/Completion/Redhat /usr/share/zsh/functions/Completion/Solaris /usr/share/zsh/functions/Completion/Unix /usr/share/zsh/functions/Completion/X /usr/share/zsh/functions/Completion/Zsh /usr/share/zsh/functions/Completion/openSUSE /usr/share/zsh/functions/Exceptions /usr/share/zsh/functions/MIME /usr/share/zsh/functions/Math /usr/share/zsh/functions/Misc /usr/share/zsh/functions/Newuser /usr/share/zsh/functions/Prompts /usr/share/zsh/functions/TCP /usr/share/zsh/functions/VCS_Info /usr/share/zsh/functions/VCS_Info/Backends /usr/share/zsh/functions/Zftp /usr/share/zsh/functions/Zle
@ctechols

This comment has been minimized.

Copy link
Owner Author

commented Jul 24, 2019

@goldylucks

It should be pasted into your own completion file.

This statement just means whichever zsh config file initializes your completion system. It will be the file that currently contains the compinit invocation. This would typically be ~/.zshrc, but may vary depending on how you ran compinstall.

I actually don't use or advocate this snippet anymore. Compinit's cache rebuilding mechanism works by counting the number of completion definitions and rebuilding the cache if that number has changed. This snippet was useful in situations where filesystem access was slow -- such as cygwin with an HDD. If you are using Mac, linux or WSL on an SSD I doubt you will notice any difference by enabling this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.