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.

tardypad 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.

Owner

ctechols 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.

ahmedre 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.

ahmedre 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.

codyhan94 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.

voyeg3r 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.

lsl 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.

danihodovic 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.

emilyst 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment