Skip to content

Instantly share code, notes, and snippets.

@ctechols
Last active January 29, 2023 07:00
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;
@duraki
Copy link

duraki commented Oct 11, 2020

Thanks @ctechols

This is how I did it.

I created this folder:

  • mkdir ~/.oh-my-zsh/plugins/zshfl

Inside I've put:

  • touch zshfl.plugin.zsh
# compinit optimization for oh-my-zsh
# 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 -U compaudit compinit
#: ${ZSH_DISABLE_COMPFIX:=true}
# ...
setopt extendedglob
if [[ $ZSH_DISABLE_COMPFIX != true ]]; then
# If completion insecurities exist, warn the user without enabling completions.
if ! compaudit &>/dev/null; then
# This function resides in the "lib/compfix.zsh" script sourced above.
    handle_completion_insecurities
# Else, enable and cache completions to the desired file.
else
if [[ -n "${ZSH_COMPDUMP}"(#qN.mh+24) ]]; then
      compinit -d "${ZSH_COMPDUMP}"
      compdump
else
      compinit -C
fi
fi
else
if [[ -n "${ZSH_COMPDUMP}"(#qN.mh+24) ]]; then
    compinit -i -d "${ZSH_COMPDUMP}"
    compdump
else
    compinit -C
fi
fi

And I've loaded it through .zshrc in plugins=(zshfs git ...)

@ftlno
Copy link

ftlno commented Oct 27, 2020

Thanks @ctechols

This is how I did it.

I created this folder:

  • mkdir ~/.oh-my-zsh/plugins/zshfl

Inside I've put:

  • touch zshfl.plugin.zsh
# compinit optimization for oh-my-zsh
# 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 -U compaudit compinit
#: ${ZSH_DISABLE_COMPFIX:=true}
# ...
setopt extendedglob
if [[ $ZSH_DISABLE_COMPFIX != true ]]; then
# If completion insecurities exist, warn the user without enabling completions.
if ! compaudit &>/dev/null; then
# This function resides in the "lib/compfix.zsh" script sourced above.
    handle_completion_insecurities
# Else, enable and cache completions to the desired file.
else
if [[ -n "${ZSH_COMPDUMP}"(#qN.mh+24) ]]; then
      compinit -d "${ZSH_COMPDUMP}"
      compdump
else
      compinit -C
fi
fi
else
if [[ -n "${ZSH_COMPDUMP}"(#qN.mh+24) ]]; then
    compinit -i -d "${ZSH_COMPDUMP}"
    compdump
else
    compinit -C
fi
fi

And I've loaded it through .zshrc in plugins=(zshfs git ...)

Thank you!

Did you mean plugins=(zshfl git ... ? :)

@divyekapoor
Copy link

Just check if the shell is an interactive shell before executing compinit?

eg. with [[ -o interactive ]] ?

@MostHated
Copy link

MostHated commented Mar 14, 2021

Skip the not really helping Ubuntu global compinit

skip_global_compinit=1

Just adding this one thing to .zshenv cut my initial loading time in half. 👍

For plugins, though. I use this: https://getantibody.github.io/ "The fastest shell plugin manager."

Plus, here are some additional tips to make initial loading faster if anyone is interested.
https://carlosbecker.com/posts/speeding-up-zsh/

(Man, I didn't realize it's already been 5 years since I came across that site. So I suppose its a bit of an old post, but most of it should still be relevant and helpful)

@smac89
Copy link

smac89 commented Nov 8, 2021

Over time, I've improved this snippet to become something like this:

function {
    # http://zsh.sourceforge.net/Doc/Release/Options.html#Scripts-and-Functions
    setopt LOCAL_OPTIONS extendedglob
    # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers
    # http://zsh.sourceforge.net/Doc/Release/Conditional-Expressions.html#Conditional-Expressions
    if [[ ! -e "${ZSH_COMPDUMP}" || -n "${ZSH_COMPDUMP}"(#qNY1.mh+24) ]]; then
        zi compinit > /dev/null
        zi cdreplay -q
    fi
}

A few notes:

  • ZSH_COMPDUMP comes from ohmyzsh, which I use in addition to zinit
  • The Y1 enables short-circuit mode so that only the first file which matches the rest of the pattern will be returned.
  • Everything is in an anonymous function so that we can enable extendedglob just for the function, and not for the entire shell

@aztack
Copy link

aztack commented Dec 14, 2021

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

works for me.

Timing with timezsh:

timezsh() {
  shell=${1-$SHELL}
  for i in $(seq 1 4); do /usr/bin/time $shell -i -c exit; done
}

Before:

        1.33 real         0.80 user         0.52 sys
        1.38 real         0.80 user         0.57 sys
        1.55 real         0.82 user         0.64 sys
        1.50 real         0.81 user         0.63 sys

After:

        0.78 real         0.32 user         0.42 sys
        0.81 real         0.33 user         0.45 sys
        0.96 real         0.37 user         0.55 sys
        1.21 real         0.42 user         0.71 sys

@xinxilas
Copy link

xinxilas commented Mar 1, 2022

It was faster here

Definitive solution?

@vincentbernat
Copy link

vincentbernat commented Apr 7, 2022

I am going for

() {
    emulate -L zsh
    setopt extendedglob
    autoload -Uz compinit complist
    local zcd=$1                # compdump
    local zcdc=$1.zwc           # compiled compdump
    local zcda=$1.last          # last compilation
    local zcdl=$1.lock          # lock file
    local attempts=30
    while (( attempts-- > 0 )) && ! ln $zcd $zcdl 2> /dev/null; do sleep 0.1; done
    {
        if [[ ! -e $zcda || -n $zcda(#qN.mh+24) ]]; then
            compinit -i -d $zcd
            : > $zcda
        else
            compinit -C -d $zcd
        fi
        [[ ! -f $zcdc || $zcd -nt $zcdc ]] && rm -f $zcdc && zcompile $zcd &!
    } always {
        rm -f $zcdl
    }
} $ZSH/run/u/$HOST-$UID/zcompdump

I didn't see a mention what happens when multiple shells are spawned at the same time. Maybe that's the reason we sometime run into empty dump file.

@medwatt
Copy link

medwatt commented Apr 12, 2022

Where exactly do we put this code:

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

@faelin
Copy link

faelin commented Jun 3, 2022

@medwatt put the snippet somewhere in your .zshrc file, or put it in an external script file and call it from your .zshrc

@thefotios
Copy link

@vincentbernat I solved the multiple shell problem by using a lockfile and a trap to remove it. This ensures that only one process is responsible for updating the file. Even though there's still a race condition, it's now almost impossibly small 1

() {
	setopt local_options
	setopt extendedglob

	local zcd=${1}
	local zcomp_hours=${2:-24} # how often to regenerate the file
	local lock_timeout=${2:-1} # change this if compinit normally takes longer to run
	local lockfile=${zcd}.lock

	if [ -f ${lockfile} ]; then 
		if [[ -f ${lockfile}(#qN.mm+${lock_timeout}) ]]; then
			(
				echo "${lockfile} has been held by $(< ${lockfile}) for longer than ${lock_timeout} minute(s)."
				echo "This may indicate a problem with compinit"
			) >&2 
		fi
		# Exit if there's a lockfile; another process is handling things
		return
	else
		# Create the lockfile with this shell's PID for debugging
		echo $$ > ${lockfile}
		# Ensure the lockfile is removed
		trap "rm -f ${lockfile}" EXIT
	fi

	autoload -Uz compinit

	if [[ -n ${zcd}(#qN.mh+${zcomp_hours}) ]]; then
		# The file is old and needs to be regenerated
		compinit
	else
		# The file is either new or does not exist. Either way, -C will handle it correctly
		compinit -C
	fi
} ${ZDOTDIR:-$HOME}/.zcompdump 

For those who are new to some of this stuff, check out

Footnotes

  1. A second process would have to check for the file in between 2 (effectively) subsequent commands. if [ -f ${lockfile} ] and echo $$ > ${lockfile}

@vincentbernat
Copy link

@thefotios my solution is already handling this case (but my message wasn't quite clear on that, so I understand you thought this was not the case). This is the purpose of the ln command (which is atomic, so no race condition).

@rafpaf
Copy link

rafpaf commented Dec 24, 2022

I commented out these lines in my .zshrc which sped it up a lot:

if type brew &>/dev/null; then
    FPATH=$(brew --prefix)/share/zsh-completions:$FPATH

    autoload -Uz compinit
    compinit
if

As this comment above pointed out, oh-my-zsh already runs compinit.

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