Boost Performance by Leveraging Byte-compilation

Emacs’s Lisp interpreter is able to interpret two kinds of code: humanly readable code (stored in files with .el extension) and machine optimized code (called byte-compiled code), which is not humanly readable. Byte-compiled code runs faster than humanly readable code. Java or .NET developers should already be familiar with the concept of byte-code, since it’s pretty central on those platforms.

You can transform humanly readable code into byte-compiled code by running one of the compile commands such as byte-compile-file. The resulting byte-code is stored into .elc files. One can also byte-compile Emacs Lisp source files using Emacs in batch mode.

Here’s how you can compile everything in your .emacs.d folder:

emacs -batch -f batch-byte-compile ~/.emacs.d/**/*.el

Of course we can easily create an Emacs command that does the same thing:

(defun er-byte-compile-init-dir ()
  "Byte-compile all your dotfiles."
  (byte-recompile-directory user-emacs-directory 0))

user-emacs-directory is an Emacs variable that points to your init dir (usually ~/.emacs.d on UNIX systems). This command will recompile even files that were already compiled before (meaning a file with the same name and the .elc extension instead of .el existed). You can try the new command with M-x byte-compile-init-dir.

You have to keep in mind that Emacs will load code from the .elc files if present alongside the .el files, so you’ll have to take steps to ensure you don’t have stale .elc files lying around. I’d suggest the following solution:

(defun er-remove-elc-on-save ()
  "If you're saving an Emacs Lisp file, likely the .elc is no longer valid."
  (add-hook 'after-save-hook
            (lambda ()
              (if (file-exists-p (concat buffer-file-name "c"))
                  (delete-file (concat buffer-file-name "c"))))

(add-hook 'emacs-lisp-mode-hook 'er-remove-elc-on-save)

This code will make Emacs delete the some_file.elc file, every time the some_file.el file in the same folder is saved.

A couple of closing notes:

  • If you don’t have any custom computationally intensive defuns in your init directory - it probably doesn’t make sense to byte-compile it.
  • Packages installed via package.el will be automatically byte-compiled during the installation process.

The code presented here is part of Prelude. As a matter of fact Prelude will byte-compile itself during the installation process (if you used the installation script, that is). Prelude will also recompile itself when M-x prelude-update is invoked.