Sunday, 22 July 2012

Emacs: Speeding Up Loading Your .emacs File

One of the best things about Emacs is its extensibility. The bulk of its editing functions are written in the same language as the editor itself, Emacs Lisp, which you, the user, can program inside the editor itself.

Experienced Emacs users invariably find packages on the Web, maybe from EmacsWiki or github, and stuff them into their .emacs file, loading them with (require). For a small number of small packages, this can often work well, but eventually you're going to either stumble across a large package like Org Mode, or for the polyglot programmer, load the major modes for half a dozen or more languages.

All those (load) and (require) calls will slow down the time taken to load your .emacs file considerably. If you want to see how long it takes, M-x emacs-init-time will give you the answer. Personally, I consider load times in excess of one or two seconds to be unacceptable for development-class hardware.

Loading What You Need, When You Need It

So how do you speed up loading of your init file, but at the same time keep everything that makes Emacs work for you? Simple: autoload and eval-after-load.

autoload tells Emacs that a function is defined in a specific file, but doesn't actually load that file until it's explicitly asked for, e.g:

(autoload 'ruby-mode "ruby-mode")

This tells Emacs that the function ruby-mode is in the file ruby-mode.el (or ruby-mode.elc, if appropriate). When the function is invoked, the path elements in load-path are checked in order to find ruby-mode.el, which is then loaded on-demand.

Automatically Using the Right Mode for a File

So, how does ruby-mode get called? Well, it's either explicitly (M-x ruby-mode), or by specifying that it should be invoked when you load an appropriate file, like this:

(add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))

This says that Emacs should enter ruby-mode whenever a file with the ".rb" extension is loaded. When it is, ruby-mode is started, and the autoload ensures that it is loaded from the appropriate file if required.

Customising Just-Loaded Code

But what happens if you want to do more than just load the major mode when .rb files are loaded? Maybe you also want to load yasnippet, ace-jump or other modules?

You've got two choices: a mode hook, or eval-after-load. The former is cleaner, but the latter is more flexible, and available in the event that there's no mode hook variable for your language (e.g. scala-mode-hook isn't available until after scala-mode is loaded).

eval-after-load takes an Emacs Lisp form as an unevaluated list and only evaluates it once the specified file has been loaded, e.g.:

(eval-after-load "ruby-mode" '(setup-autocompletion))

The time-saver here comes from the fact that the quoted form is evaluated after load, so it can potentially result in quite a lot of work getting done when it's loaded, and you'll only feel the effects when you enter that specific mode.

In this case, the same effect can be achieved by adding a function to ruby-mode-hook:

(add-to-list 'ruby-mode-hook #'setup-autocompletion)

Here, I'm assuming that (setup-autocompletion) has already been defined and, as in my case, is referred to by multiple programming modes (nb: that function is one I've written myself; it's not part of Emacs, so you won't have it unless you've written one with the same name!). The form can be atribrarily complex, but it must be a single form. Multiple forms can be wrapped in a (progn) to compose them. Here's an example for cperl-mode that's more involved, setting some defaults, defining some Perl-specific functions, and doing that (setup-autocompletion) again.

(eval-after-load "cperl-mode"
     cperl-merge-trailing-else nil
     cperl-continued-statement-offset 0
     cperl-extra-newline-before-brace t)
    (defun installed-perl-version ()
      (let ((perl (executable-find "perl")))
        (if perl
            (shell-command-to-string (concatenate 'string perl " -e '($v = $]) =~ s/(?<!\\.)(?=(\\d{3})+$)/./g; print $v;'")))))
    (defun use-installed-perl-version ()
      (let ((perl-version (installed-perl-version)))
        (if perl-version
              (let ((case-fold-search nil))
                (re-search-forward "^use [a-z]" (point-max) t)
                (open-line 1)
                (insert (concatenate 'string "use v" perl-version ";"))))
            (message "Couldn't determine perl version"))))

eval-after-load isn't limited to programming modes. Here's an example of how you can auto-load your credentials when loading GNUS:

(eval-after-load "gnus"
    nntp-authinfo-file "~/.authinfo"
    gnus-nntp-server *my-nntp-server*))


So, there you have it:

  • auto-mode-alist allows you to enter specific modes based on file extension
  • autoload allows you to not not load that mode until it's actually required by opening a file of that type
  • eval-after-load and/or (add-to-list {mode}-mode-hook) can be used for mode customisation or setup, again only when the mode actually loads

As much as possible, ensure that your .emacs file makes minimal use of non-deferred (require) or (load) calls, although (require 'cl) is an exception. It's also fair to load code/minor modes that you use all the time, since there's no point in deferring them just to load them the first time you do anything!

No comments:

Post a Comment