Recently, I had the urge to optimize the startup time of my emacs configuration. I managed to reduce my startup time from three seconds to under half a second. It was a fun exercise even though I don't really start emacs that often. Even that minor reduction makes starting a new emacs instance (e.g., from mutt or in a terminal) much more pleasant. I could use the server functionality in emacs (along with emacsclient) to always connect to a single persistent instance, but I like the isolation of separate processes. I've had the server crash before bringing down all of my sessions, which is fairly annoying. Most of the startup time savings came from systematically employing lazy package loading through John Wiegley's excellent use-package. Besides making it easy to properly load packages lazily, use-package also handles package installation and configuration.

use-package encourages a configuration style that isolates the configuration for each package in a use-package form. Moreover, the configuration of each package is split into initialization steps taken at emacs startup time (via the :init section) and configuration steps executed after the package is loaded (via the :config section). Packages can also be loaded lazily, which significantly improves startup time. By default, use-package forms that bind keys or are associated with a specific file type are loaded lazily.

After doing a bit of profiling with esup, which is an emacs startup profiler, I found a few surprising initialization steps that were taking up most of my startup time. The most expensive parts are difficult to optimize further: theme loading, font setting in GUI mode, and initialization of the package library. Aside from that, cc-mode was one of the most expensive things to load; moving more of the setup from :init to :config took care of that.

There was at least one interesting and unexpected consequence of my use-package configuration that I did not initially appreciate. I decided to set

(setq use-package-always-ensure t)

This option tells use-package to automatically download and install packages that are not already installed. This is convenient to bootstrap a new emacs installation or to bring an existing one up to date with the latest changes to your emacs configuration. It has side effect of breaking when the package being set up is not in one of the emacs package repositories (i.e., local packages). This came up while setting up a major mode for netlogo. It eventually occurred to me that I could tell use-package to not try to install this package by setting :ensure nil in the use-package form:

(use-package netlogo-mode
  :ensure nil
  :load-path "~/.config/local/emacs.d/lisp"
  :mode ("\\.nls$\\|\\.nlogo$" . netlogo-mode))

In the process, I cleaned out old configurations I didn't need anymore and enabled a few new packages including:

  • swiper (for sophisticated finding and replacing)
  • avy (for quick navigation)
  • switch-window (for faster movement between windows)

It was fun to refresh my entire configuration. I should probably do that more often.