System Crafters

How to Cut Emacs Startup Time in Half

Why is Emacs so slow to start up?

Is it actually slow with no config? Run emacs -Q to find out!

So why is it so much slower with our configuration?

Let's find out how long it's taking!

Add a function to emacs-startup-hook to print out the duration of Emacs startup:

(defun efs/display-startup-time ()
  (message "Emacs loaded in %s with %d garbage collections."
           (format "%.2f seconds"
                   (time-subtract after-init-time before-init-time)))

(add-hook 'emacs-startup-hook #'efs/display-startup-time)

All startup behavior is happening in the normal-top-level function!

A helpful manual page is Summary: Sequence of Actions at Startup.

The most important tip: don't load any packages!

use-package gives you a few different ways to defer package loading:

  • :hook - Package will be loaded the first time one of the hooks is invoked
  • :bind - Package will be loaded the first time one of the key bindings is used
  • :commands - Package will be loaded when one of the commands are used
  • :mode - Package will be loaded the first time a file with a particular extension is opened
  • :after - Load this package after other specific packages are loaded
  • :defer - If you don't use any of the other options, this one will defer loading until after startup

There are a few other options use-package provides, but these are all the most likely ones you would use.

The strategy is to look at all of your use-package expressions and decide whether it really needs to be loaded immediately at startup!

If you want to make sure a package gets loaded at startup despite the use of any of the options above, use :demand t.

Let's try it!

"fde" '(lambda () (interactive) (find-file (expand-file-name "~/.emacs.d/")))

with-eval-after-load can be useful!

with-eval-after-load is a macro that causes a section of code to be executed only after a particular package gets loaded.

(with-eval-after-load 'org
      '((emacs-lisp . t)
      (python . t)))

  (push '("conf-unix" . conf-unix) org-src-lang-modes))

If you need to split up your configuration into multiple sections, this is one way to ensure you don't accidentally cause a package to load too early!

Tweaking the garbage collector

One other common performance trick is to reduce the number of times the garbage collector will run during the startup process.

Set the gc-cons-threshold high at the beginning of your init.el:

;; The default is 800 kilobytes.  Measured in bytes.
(setq gc-cons-threshold (* 50 1000 1000))

Then bring it back down at the end of your init.el:

;; Make gc pauses faster by decreasing the threshold.
(setq gc-cons-threshold (* 2 1000 1000))

Another option is to use Andrea Corrallo's gcmh package to manage it automatically.