Skip to content

Instantly share code, notes, and snippets.

Last active June 15, 2024 19:43
Show Gist options
  • Save ctechols/ca1035271ad134841284 to your computer and use it in GitHub Desktop.
Save ctechols/ca1035271ad134841284 to your computer and use it in GitHub Desktop.
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( ]]; then
compinit -C;
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

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}(${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 
		# Exit if there's a lockfile; another process is handling things
		# Create the lockfile with this shell's PID for debugging
		echo $$ > ${lockfile}
		# Ensure the lockfile is removed
		trap "rm -f ${lockfile}" EXIT

	autoload -Uz compinit

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

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


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

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

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

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

Copy link

@aztack Thank you for your snippet, it helps alot :)

Copy link

Fellas, if you're of that kind that checks their .zsh files and scripts with shellcheck (like I am), here's a more POSIX-compliant (as much as zsh allows) statement:

if [ "$(find ~/.zcompdump -mtime 1)" ] ; then
compinit -C

or oneliner, if you prefer that:

# negation, so that at least one exits on 0
[ ! "$(find ~/.zcompdump -mtime 1)" ] || compinit
compinit -C

Copy link

eeweegh commented Sep 16, 2023

find's manpage is not clear on this, but I believe you want -mtime +1 to catch a file at least 24h old, rather than exactly 24h old.
On OSX, my .zcomdump was several days old, and the above would not trigger until I added the '+'.

Copy link

nimitagr commented Oct 1, 2023

@aztack It helped. 🙇

Copy link

niqodea commented Jan 21, 2024

This is my take on the problem, it's a tradeoff between efficiency and simplicity:

autoload -Uz compinit; compinit -C  # Use cache to reduce startup time by ~0.1s
# Have another thread refresh the cache in the background (subshell to hide output)
(autoload -Uz compinit; compinit &)

Despite the obvious pitfall (having the shell start another thread at startup), I wonder if it's overall a good solution 🤔

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