I recently got sick of opening a new tab and waiting seconds before my shell prompt showed up. In order to prevent my future self from wasting valuable seconds of my life, I went through the trouble of profiling my zsh configuration. After extensive use of zprof, I determined my bottleneck: sourcing command completion files, but only the ones that executed a program to get their contents.

zsh profile

Feast your eyes on the marvel that is modern software engineering:

source <(kubectl completion zsh)

I am singling out the kubectl tool for Kubernetes, as it is a fairly common source of aggravation in my life. And while I may come off sounding extremely sardonic, I am genuinely trying to convince you that there is value in analyzing what appears to be such an insignificant snippet of shell code.

For those unfamiliar, this code snippet is intended for loading completion functions into your shell. Completion functions are what enable users to press the tab key on their keyboard and receive text predictions for specific command-line flags and values. In this case, the program has a command, kubectl completion zsh, that outputs the code required to teach zsh about all of the flags available from kubectl. The source command is a built-in shell function that evaluates shell scripts. The scary looking <() voodoo is doing nothing more than redirecting the output of the completion command into the input for the source function.

This strategy for extending users’ shells isn’t exclusive to kubectl. At first glance, it appears to be a great way to ship shell completion files; users never have to worry that the version of the completion code and the version of the software are mismatched. However, every design decision has trade-offs.

The performance cost of sourcing completion files is no news to the zsh developers. Zsh has a built-in completion system that includes the caching of processed completion files into dump-files, e.g. ~/.zcompdump, such that opening future shells can avoid the overhead of reprocessing everything from scratch. Code used for completion is usually stored as a file in directories listed in the $fpath environment variable.

Herein lies the trade-off for programs that embed their own completion files: the zsh completion system only caches completion files collected from disk. This means any tool that tells you to source their shell scripts, is unlikely to be cached and going to add significant performance overhead.

So, how do you stop tools like kubectl from destroying my shell? Convince the software’s maintainers to package completion files with the software. In this case, the homebrew package for kubectl actually installs a zsh completion file into a directory on the $fpath. Rejoice, we can safely remove the hellish code snippet from our ~/.zshrc file and still tab complete kubectl.

Finally, let’s compare benchmarks between my shell first sourcing a few tools, namely pyenv, kubectl, and helm, then my shell loading completion for these from cached files:

zsh benchmark

Sources / Interesting Follow-Ups: