[emacs-berlin] Understanding hooks

Michael Heerdegen michael_heerdegen at web.de
Tue Sep 10 15:39:46 UTC 2024


jman <emacs-berlin at city17.xyz> writes:

> Hi everyone!

Hi.

> Is adding hooks /per se/ something that slows emacs down? What is the
> runtime cost of a hook? Are there hooks that are on "hot paths" and
> therefore better not adding additional processing there?

You seem to mean "adding functions to any existing hooks".

Running a hook takes as long as running all members.  Whether this is
long, or too long, totally depends on the hook, and what you find
acceptable.

Hooks that are run very frequently, like, for every redisplay, obviously
need to be used more carefully than hooks that are run like once when
quitting Emacs.

Use common sense and avoid doing expensive jobs more often than needed.
Really, no magic involved here.

However, an important task is to identify the correct hook to use.  And
to avoid side effects or recursive hook invocations.  Test your
configuration and don't forget about your change.


> For example, I have a few hooks triggered when switching major-mode:
>
> (add-hook 'prog-mode-hook (lambda () (display-line-numbers-mode 1)))
> (add-hook 'text-mode-hook (lambda () (display-line-numbers-mode 1)))
>
> These ensures that line numbers are displayed in all text- and prog-
> major modes (and
> derivatives). I assume these are pretty harmless, correct?

I think so - but better avoid adding anonymous functions to hooks.
Depending on partly random circumstances a repeated call would add a
function multiple times to the hook.  The hook value is also easier to
inspect when it contains only named members.  In the above case

  (add-hook 'prog-mode-hook #'display-line-numbers-mode)
  (add-hook 'text-mode-hook #'display-line-numbers-mode)

should be fine - minor mode functions enable the mode unconditionally
when called without arguments.


> I am working on adding a bit more invasive hooks, like "run a function
> when saving a buffer, but
> only in a specific major-mode (and derivatives)". This came from an
> insightful comment[0] from
> Tassilo Horn in the mu4e issue tracker:
>
> (defun remove-before-save-hook ()
>  (remove-hook 'before-save-hook 'whitespace-cleanup))
>
> (defun add-before-save-hook ()
>  (add-hook 'before-save-hook 'whitespace-cleanup))
>
> ;; Do not run `whitespace-cleanup` when saving a buffer in message-mode (and derivatives)
> (add-hook 'message-mode-hook 'remove-before-save-hook)
>
> ;; Run whitespace-cleanup when saving buffers only on text- and prog- major modes
> (add-hook 'prog-mode-hook 'add-before-save-hook)
> (add-hook 'text-mode-hook 'add-before-save-hook)
>
> Does anybody see an obvious code smell?

If you have read the documentation, you already know about buffer local
and global hook bindings.

You manipulate the global hook values above - this is obviously not
correct if you are using multiple buffers (what one typically does in
Emacs...).  `add-hook' and `remove-hook' provide an optional argument to
manipulate the local hook bindings.  Use it.

> Another approach I thought was to plug something to
> `after-change-major-mode-hook` but I suspect it
> would add unnecessary runtime cost.

Depends on "something".  Hooks are there to be used, obviously.

`after-change-major-mode-hook' is not run that often, FWIW.  You don't
need to be overly cautious.  More important: know in which state Emacs
or your buffer is when any hook is run.  For example, when
`after-change-major-mode-hook' is run, the buffer setup you are
accustomed to is not complete.


> Another question is: is there a way to "benchmark" how a hook is
> affecting runtime Emacs performances? Maybe with
> `profiler-{start,stop}`?

For example.  Depends on the case.  Benchmarking hooks is not different
from benchmarking in general.


Michael.


More information about the emacs-berlin mailing list