While I mostly run emacs as a daemon managed by systemd I like to keep my configuration nimble enough that I can just start emacs and have an ad-hoc session for editing some files.1 Or that’s the goal. This has been a long running project for me, but early on in quarantine times, I’ve gotten things tightened up to the point that I feel comfortable using EDITOR=emacs just about all the time. On my (very old) computers, emacs starts and in well under 1 or 2 seconds, and does better on faster machines. If your emacs setup takes a long time to start up, this article is for you! Let’s see if we can get the program to start faster.

Measure!

The emacs-init-time function in emacs will tell you how long it took emacs to start up. This tends to be a bit under the actual start time, because some start-up activity gets pushed into various hooks, after startup. Just to keep an eye on things, I have a variant of the following as the first line in my config file: :

(add-to-list 'after-init-hook
      (lambda ()
        (message (concat "emacs (" (number-to-string (emacs-pid)) ") started in " (emacs-init-time)))))

It’s also possible to put additional information in this hook (and in reality I use something that pushes to a desktop notification tool, rather than just to message/logging.

You can also lower this number by putting more work into the after-init hook, but that sort of defeats the purpose: if you get the init time down to a second, but have 10 seconds of init-hook runtime, then that’s probably not much of a win, particularly if the init-hook tasks are blocking.

The second thing to do is have a macro2 available for use that allows to measure the runtime of a block of code, something like: :

(defmacro with-timer (name &rest body)
  `(let ((time (current-time)))
 ,@body
 (message "%s: %.06f" ,name (float-time (time-since time)))))

You can then do something like: :

(with-timer "mode-line-setup"
  (set-up-mode-line))

And then look in the *Message* buffer to see how long various things take so you know where focus your efforts.

Strategies

  • use-package makes it possible to do much less during applciation startup, and makes it possible to load big lisp packages only when you’re going to use them, and registers everything so you never feel the difference. Audit your use-package forms and ensure that they all declare one of the following forms, which insures that things are loaded upon use, rather than at start time:
    • :bind registers specific keybindings, but as autoloads so that the pacakge is only loaded when you use the keybinding.
    • :command registers function names as autoloads, like keybinding, so the commands/functions are available but again the package isn’t loaded until it’s used.
    • :mode attaches the package to a specific file extension or extensions so again, the package doesn’t get loaded until you open a file of that type.
    • :after for loading a package after another package it depends on or is only used after.
    • :hook this allows you to associate something from this package into another package’s hook, with the same effect of deferring loading of one package until after another is used.
  • Avoid having the default-scratch buffer trigger loading as much as possible. It’s very tempting to have *scratch* default to org-mode or something, but if you keep it in fundamental-mode you can avoid loading most of your packages until you actually use them, or can avoid pulling in too much too quickly.
  • Keep an eye on startup both for GUI, terminal, and if you use daemons, daemon instances of emacs. There are some subtleties that may be useful or important. It’s also the case that terminal startup times are often much less than GUI times.
  • Think about strategies for managing state-management (e.g. recentf session-mode and desktop-mode.) I’ve definitley had times when I really wanted emacs to be able to restart and leave me exactly where I was when I shut down. These session features are good, but often it’s just crufty, and causes a lot of expense at start up time. I still use desktop and session, but less so.
  • Fancy themes and modelines come at some cost. I recently was able to save a lot of start up time by omiting a call to spaceline-compile in my configuration. Theme setup also can take a bit of time in GUI mode (I think!), but I dropped automatic theme configuration so that my instances would play better with terminal mode.3

Helpful Settings

I think of these four settings as the “start up behavior” settings from my config: :

(setq inhibit-startup-echo-area-message "tychoish")
(setq inhibit-startup-message 't)
(setq initial-major-mode 'fundamental-mode)
(setq initial-scratch-message 'nil)

I’ve had the following jit settings in my config for a long time, and they tend to make things more peppy, which can only help during startup. This may also just be cargo-cult, stuff: :

(setq jit-lock-stealth-time nil)
(setq jit-lock-defer-time nil)
(setq jit-lock-defer-time 0.05)
(setq jit-lock-stealth-load 200)

If you use desktop-mode to save state and re-open buffers, excluding some modes from this behavior is good. In my case, avoiding reopening (and rereading) potentially large org-mode buffers was helpful for me. The desktop-modes-not-to-save value is useful here, as in the following snippet from my configuration: :

(add-to-list 'desktop-modes-not-to-save 'dired-mode)
(add-to-list 'desktop-modes-not-to-save 'Info-mode)
(add-to-list 'desktop-modes-not-to-save 'org-mode)
(add-to-list 'desktop-modes-not-to-save 'info-lookup-mode)
(add-to-list 'desktop-modes-not-to-save 'fundamental-mode)

Notes


  1. Ok, ok, I live with a vim user and the “quick startup time,” situation is very appealing. ↩︎

  2. I tweaked this based on what I found this on stack overflow which stole it from this listserv post. ↩︎

  3. Mixed GUI/Terminal mode with themes can be kind of finicky because theme settings are global and color backgrounds often won’t play well with terminal frames. You can say “if this is a gui mode,” but that doesn’t play well with daemons, and hooking into frame-creation has never worked as seamlessly as I would expect, because the of the execution context the hook functions (and slowing down frame creation can defeat part of the delight of the daemon.) ↩︎