Speeding up `M-x man` in Emacs on MacOS
Table of Contents
Problem Description
Running `M-x man` in Emacs on MacOS takes a significant amount of time compared to Linux. On my Macbook Pro M4 laptop, it takes ~0.673 seconds.
Understanding `man.el`
The `man` function comes with Emacs, and can be found in your Emacs' installation's `lisp/man.el` file. A quick summary of the flow of the function is as follows:
- The `man` lisp function is invoked, possibly with an argument.
- The position of the cursor is used to guess what the user is about to look up.
- The `Man-completion-cache` is set to nil. This variable is defined at the top level of the file via `defvar`.
- The function `Man-completion-table` is called. This function is responsible for the bulk of the logic of displaying the possible completions, and filtering down the list via user input. This is the function that sets the `Man-completion-cache` list via running `man -k ^` to get the full list of possible man pages. This call to the external `man` process is what takes so long.
So… why is `man -k ^` so slow on Mac?
It turns out that `man` on Mac is not a compiled binary like on Linux, but a shell script that greps pages in "$MANPATH". Also unlike Linux, `man` on Mac does not use mandb to index the search path ahead of time.
The solution
We can make use of Emacs' advice functionality to remember the value of the Man-completion-cache in between calls. See the example below:
(defvar my/Man-completion-cache nil) (defun remember-man-completion-cache (man-function &rest args) (unless Man-completion-cache (setq Man-completion-cache my/Man-completion-cache)) (let ((result (apply man-function args))) (setq my/Man-completion-cache Man-completion-cache) result)) (advice-add #'Man-completion-table :around #'remember-man-completion-cache)
Miscellaneous learnings/thoughts
- `defvar` is very weird. A variable bound via `defvar` is globally redefined when defined in a `let` binding.
- Implementing a faster `man` on Macos would make for an interesting side project.