Optimize emacs start up time


Introduction

Emacs is a great tool. However, its loading time can grow huge. In this post, I’ll show you some configurations that helped me reduce the start up time from ~2.45s to ~0.81s.

Optimization steps

To make it more interesting, I’m going to add a section for each optimization along with the time it saved me. That way, you can decide which optimizations you would like to apply.

Base configuration

The base configuration requires ~2.45s to initialize Emacs. If you check it, be aware that I’m not tangling the section Writing Research in LaTeX. If you tangle it, it needs even more time to load. That’s the starting point.

Don’t manipulate UI in init.el

Manipulating the UI is expensive: disabling UI elements, modifying the font, the theme, etc. I changed a couple of things related to the UI.

  1. Remove the code that hides some of the default UI elements from Emacs.

    (scroll-bar-mode -1)
    (tool-bar-mode -1)
    (tooltip-mode -1)
    (menu-bar-mode -1)
    (column-number-mode)
    (set-face-attribute 'default nil :height 180)
    (toggle-frame-maximized)
    
  2. Remove the code that loads the theme.

    (use-package flatland-theme
      :config
      (load-theme 'flatland t))
    

With that, we go down to ~2s.

Garbage collector

Emacs uses 8MB as the default value for the garbage collector. As a result, Emacs will call the garbage collector too many times during the start up. Doom emacs has a trick to improve that. We need to add the following code to early-init.el.

(setq gc-cons-threshold 63000000
      gc-cons-percentage 0.6)

With that, we go down to ~1.021s. Half the time from the previous step.

Disable UI

We can hide some UI elements in the early-init.el to gain some performance.

(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)

With that, we go down to ~0.92s.

Autoload packages

Never require packages. Use use-package to autoload them. For example, we can load the magit the first time we call magit-status.

(use-package magit
  :bind ("C-x g" . magit-status))

With that, we go down to ~0.91s. It’s anecdotal in my case, but it can save you a lot of time if you are using dozens or hundreds of packages.

Tangle org file

Normally, Emacs reads the configuration from the init.el file. However, we can use org mode to include comments with the code. Personally, I had my configuration in a file called config.org. I loaded it from my init.el with the following line of code:

(org-babel-load-file "~/dotfiles/emacs/config.org")

It’s pretty convenient, but slow. We can make it faster by tangling the file directly into the init.el. In other words, we can extract the source code blocks from the org file and create the init.el with them.

# -*- after-save-hook: (org-babel-tangle) -*-
#+property: header-args:elisp :exports code :results none :tangle init.el

With that, we go down to ~0.82s and get the final config.

Conclusion

Emacs is easy to configure, but hard to optimize. Try all the tricks above and check for some more on the internet.