Emacsを世界最速級で起動する方法

Emacs Advent Calendar 2022 19日目です。

Vim Advent Calendar 2022 3日目の 爆速で起動する Neovim を作る に触発されて、「自分もEmacs版を書くぞ!」ということで書いていきます。

なお、VimとEmacsの比較をし易くするためになるべく文章の構成を寄せて書くことをご了承ください。

始めに

「Emacsを高速に起動する」ということに対して、多くのEmacsユーザーは興味がありません。 本来Emacsというのは常に起動し続けるものであり、必要に応じてEmacs Lispを適用し、テキストエディタをカスタマイズしていくものです。 現に「Emacs, 起動、 高速化」と検索すると、「4000msを1000msにした」のような秒単位での高速化の記事ばかりみつかります。

もしあなたが設定を変えずに手軽に高速に起動したい場合は emacs --daemon でdaemonを立ち上げ、 emacsclient でつなぐようにすれば良いでしょう。 私は他人のdotfilesを読むのが趣味なのですが、多くのEmacsユーザーは EDITOR=emacsclient と設定しています。

また、起動時間のみを考慮するなら ~/.emacs.d/init.el を削除して素で起動すれば良いでしょう。 そうなると純粋にマシンスペックの勝負になります。 Emacsは素の状態でも十分魅力的な機能を多数盛り込まれているが、カスタマイズしてこそ真価を発揮するエディタなのでアプローチとしてはイマイチでしょう。

この記事を読む前に Emacs の起動時間を"“詰める”" を熟読することを推奨しています。 特に以下のChapterは非常に有益なテクニックが詰め込まれており、大幅な速度改善を見込めます。

本のあらすじにも書いてある通り、Emacs自体にすでに大量のファイルがある状態で高速に起動する時点で自分の設定を見直さなければならないことは明白でしょう。

しかし、考えてみれば Emacs には 1000 以上の Emacs Lisp ファイルが初めから同梱されているわけで、そこに数十のプラグインを足しただけで爆裂に遅くなるのは、なにか設定にも問題がある気がします。

詳細は後述しますが、現在の私のEmacsは15〜25ms程度で起動をします。 一切設定を読まずに素で起動するコマンド emacs -Q で起動させると2ms程度です。 まだまだ高速に起動させる余地はありますが、「高速に起動させることができている」と自負しても問題ないでしょう。

Emacs booting time: 20 [msec] = ‘emacs-init-time’.
Loading init files: 10 [msec], of which 1 [msec] for ‘after-init-hook’.

前提

環境

2022年12月現在、 Macbook Pro 16-inch, 2019 の標準モデルを使っています。

$ neofetch
                    'c.          take@obaranoMacBook-Pro.local
                 ,xNMM.          -----------------------------
               .OMMMMo           OS: macOS 13.1 22C65 x86_64
               OMMM0,            Host: MacBookPro16,1
     .;loddo:' loolloddol;.      Kernel: 22.2.0
   cKMMMMMMMMMMNWMMMMMMMMMM0:    Uptime: 1 day, 18 hours, 29 mins
 .KMMMMMMMMMMMMMMMMMMMMMMMWd.    Packages: 347 (brew)
 XMMMMMMMMMMMMMMMMMMMMMMMX.      Shell: fish 3.5.1
;MMMMMMMMMMMMMMMMMMMMMMMM:       Resolution: 1792x1120@2x
:MMMMMMMMMMMMMMMMMMMMMMMM:       DE: Aqua
.MMMMMMMMMMMMMMMMMMMMMMMMX.      WM: Quartz Compositor
 kMMMMMMMMMMMMMMMMMMMMMMMMWd.    WM Theme: Blue (Dark)
 .XMMMMMMMMMMMMMMMMMMMMMMMMMMk   Terminal: tmux
  .XMMMMMMMMMMMMMMMMMMMMMMMMK.   CPU: Intel i7-9750H (12) @ 2.60GHz
    kMMMMMMMMMMMMMMMMMMMMMMd     GPU: Intel UHD Graphics 630, AMD Radeon Pro 5300M
     ;KMMMMMMMWXXWMMMMMMMk.      Memory: 9716MiB / 16384MiB
       .cooc,.    .,coo:.

Emacsのversionは 30.0.50 です。 日々HEAD buildをしているので最新のversionとは微妙に差異がありますが、起動速度にはそこまで大きな差が出ないでしょう。

$ emacs -version
GNU Emacs 30.0.50
Development version 6a390fd42ec4 on master branch; build date 2022-12-17.
Copyright (C) 2022 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

設定について

2022年現在、 init.elearly-init.el は6800行(空白を除くと5800行)程度あります。 設定を別ファイルに切り分けたりしておらず、 init.el 1つで管理をしています。

すべての設定を org-mode で管理しており、GitHub ActionsでbuildをしGitHub Pagesにhostingしています。

el-get で導入しているpackage数は384個です。

$ ls -l ~/.emacs.d/el-get | wc -l
     384

起動速度の測り方

そもそも起動速度とはなんなのか

Emacsはざっくり以下のような起動プロセスを踏みます。

Emacs自体にpatchを当てない限り、 ~/.emacs.d/early-init.el より前には手が出せません。 lisp/proced.el のような既存のEmacs Lispファイルはportable dumperですでに実行可能なbinaryに出力されており、fileをloadすることなく実行されているようです。

Emacsのbuild時に自分の init.el を読み込ませて pdump を生成するのが真の最速であり、5ms以内での起動を期待できます。 たとえ記述がほぼないとしても init.elearly-init.el を読み込み実行するというのは重いもので、File I/Oというのは重いものだと実感させられました。 しかし、それではEmacsのカスタマイズ性というのを殺すことになるので今回は含めないこととします。

計測方法

init.el に以下のようなコードを記述します。

(defconst my/before-load-init-time (current-time))

;;;###autoload
(defun my/load-init-time ()
  "Loading time of user init files including time for `after-init-hook'."
  (let ((time1 (float-time
                (time-subtract after-init-time my/before-load-init-time)))
        (time2 (float-time
                (time-subtract (current-time) my/before-load-init-time))))
    (message (concat "Loading init files: %.0f [msec], "
                     "of which %.f [msec] for `after-init-hook'.")
             (* 1000 time1) (* 1000 (- time2 time1)))))
(add-hook 'after-init-hook #'my/load-init-time t)

(defvar my/tick-previous-time my/before-load-init-time)

;;;###autoload
(defun my/tick-init-time (msg)
  "Tick boot sequence at loading MSG."
  (when my/loading-profile-p
    (let ((ctime (current-time)))
      (message "---- %5.2f[ms] %s"
               (* 1000 (float-time
                        (time-subtract ctime my/tick-previous-time)))
               msg)
      (setq my/tick-previous-time ctime))))

(defun my/emacs-init-time ()
  "Emacs booting time in msec."
  (interactive)
  (message "Emacs booting time: %.0f [msec] = `emacs-init-time'."
           (* 1000
              (float-time (time-subtract
                           after-init-time
                           before-init-time)))))

(add-hook 'after-init-hook #'my/emacs-init-time)

参考記事はこちら。

そうすると起動後 *Minibuffer* に起動時間の出力がされるはずです。

Emacs booting time: 20 [msec] = ‘emacs-init-time’.
Loading init files: 10 [msec], of which 1 [msec] for ‘after-init-hook’.

評価のし方

高速化する上で重要なのは、評価指標をマシンスペックに依存しない形で評価をすることでしょう。

以下のような init.el を作成すれば、起動時にどのような処理が行われているのか知ることができます。

init.el:

(require 'profiler)
(profiler-start 'cpu)

;;; --------- 処理中略 ---------

(profiler-report)
(profiler-stop)

上記のような、処理が空の init.el を用意して起動すると以下のようなreport bufferが起動するでしょう。

Samples    %   Function
      7 100% - normal-top-level
      7 100%  - command-line
      7 100%   - startup--load-user-init-file
      7 100%    - load
      7 100%       byte-code
      0   0% + ...

多少ブレがあるものの、自分の init.el で実行をするとSamples数が15以内で起動します。 Sample数が素に近ければ近いほど、高速に動いているといえます。

この記事で達成する目標

皆さんのEmacsの起動速度はどうでしょうか? 私の感覚になりますが、だいたい以下のように分類できるでしょう。

  • 5000ms以上
    • だいぶ遅い
    • 外部への通信(パッケージの更新等)が多数走ってしまっている可能性が高い
  • 1000ms 〜 5000ms
    • 一般的な速度
    • だいたいのEmacsユーザーはこの辺だろう
    • パッケージ管理ツールを普通に使って普通に設定しているとこのくらい
  • 100ms 〜 1000ms
    • パッケージ管理ツールで高速化をするとだいたいこの辺になる
    • そこそこ頑張る必要がある
  • 100ms以下
    • すべての設定をパッケージ管理ツールを使わずに素で書いているか、高速化にこだわった設定をしている
    • かなり頑張る必要がある
    • EDITOR=emacs 設定するのを視野に入れられる

NeoVimの場合はこちら。 Neovimでは「50ms以下」から「500ms以上」を話題にしているので明らかにEmacsはスタート地点が遅い。

この記事では当然100ms以下を目指している。

具体的なアプローチ方法

パッケージ管理ツールについて

多くの人は use-packageleaf を使っていることでしょう。 use-package の実態はパッケージ管理ツールというよりはmacroです。

use-package 内に適切の設定を記述すると、パッケージを落としパスを通し、関数や変数の設定を記述したりする s式 を生成してくれます。 私は use-package には疎いので正確なことは書けませんが、~s式~ の効率が良いかといわれたら最高速をたたき出せるものではありません。

普通にEmacsの設定をするなら間違いなく使うべきものですが、今回のようなパフォーマンスを求める場合あまりお勧めできるものではありません。

NativeComp

EmacsをFull NativeCompでBuildする

多くの人はHomebrewのようなパッケージマネージャーで落してBuildしたEmacsを使っていることでしょう。 それだと細かいbuildの設定もできないし、かゆい所に手が届きません。

まずは git clone をします。

$ git clone git://git.sv.gnu.org/emacs.git
$ cd emacs

次にNativeCompでbuildします。 私は毎日 git pull したうえで以下のコマンドを叩いてbuildしています。

$ ./autogen.sh && ./configure --with-native-compilation=aot --without-ns --without-x --with-libxml2=/usr/bin/xml2-config && make -j8
$ sudo make install

--with-native-compilation=aot が特に重要です。

ネイティブコンパイルEmacsの登場に書いてありますが、2021年4月ころにNative Compがサポートされました。 foo.el というファイルから foo.eln という拡張子のファイルを生成します。 brew install libgccjit などをしてちゃんと libgccjit をinstallする必要があります。 EmacsのNative Compilationの性能を測定するでも検証されているが、かなりの高速化が期待できます。

Add –with-native-compilation=aot configuration optionaot を指定してbuildするとEmacsのFull Native Compが実行されるようになりました。 make -j <proc> でproc数を多くすると処理が重すぎてPCが固まるので少なめに設定しておく方が良いです。

NativeCompした結果のファイル(eln)が優先的に読み込まれる

Emacs Lispで別のファイルを呼び込む際に (load "/path/to/dir/file") のように書きます。

実際に、command-lineから init.elearly-init.el を読み込む時にも load は使われています。

コード(startup–load-user-init-file)はここです。

load関数の定義 を見てみると、優先的に .eln を読むようになっているようなので早めにNativeCompする必要があります。

init.elとearly-init.elをbyte-compileする

バイトコンパイル - Emacs の起動時間を"“詰める”"について。

Byte Compileのドキュメントには以下のような記述があります。

Emacs Lispには、Lispで記述された関数を、より効率的に実行できるバイトコード(byte-code)と呼ばれる特別な表現に翻訳するコンパイラー(compiler)があります。コンパイラーはLispの関数定義をバイトコードに置き換えます。バイトコード関数が呼び出されたとき、その定義はバイトコードインタープリター(byte-code interpreter)により評価されます。

こんな感じでbyte-compileすると良いです。

$ emacs -Q --batch -f batch-byte-compile early-init.el
$ emacs -Q --batch -f batch-byte-compile init.el

基本的にはnative compが優先的に読まれるので意味がないといわれたらそうなのですが、後述する el-get はbyte-compile時に発行させるので流す必要があります。

NativeCompの設定

native-comp-speednative-comp-async-jobs-number を設定すると良いです。

native-comp-speed は最適化オプションで0〜3があります。 ソースコードはこちら。 「Warning: with 3, the compiler is free to perform dangerous optimizations.」と書いてありますが、半年以上この設定で問題なく使えているので気にしなくて良いでしょう。

native-comp-async-jobs-number はjob数で大きめに設定するとPCが極端に重くなってしまうので低めに設定しておくことをお勧めします。

(with-eval-after-load 'comp
  (setq native-comp-async-jobs-number 8)
  (setq native-comp-speed 3))

init.elとearly-init.elをNativeCompする

native-compile-async でNativeCompileできます。 コンパイル結果の出力は *Async-native-compile-log* bufferです。

(native-compile-async "~/.emacs.d/init.el")
(native-compile-async "~/.emacs.d/early-init.el")

early-init.elについて

early-init.el - Emacs の起動時間を"“詰める”"について。

early と書いてある通り、初期段階で読み込まれるものです。 「初期段階」で読み込まれるとは具体的に何でしょうか? early-init.el に書くべき処理とそうでない処理の違いとはなんなのでしょうか?

49.4.6 The Early Init Fileには以下のように記述されています。

By contrast, the normal init files are read after the GUI is initialized.

要するに「GUIを初期化するより前に読み込まれる」としか書いてないです。

early-init.elinit.el が読み込まれる間のコードを読むしかないです。

https://github.com/emacs-mirror/emacs/blob/6a390fd42ec4ef97d637899fc93f34ea65639e3c/lisp/startup.el#L1369-L1479

実行されている主な関数は以下。

  • startup–update-eln-キャッシュ
  • package-activate-all
  • window-system-initialization
  • frame-initialize
  • tool-bar-setup
  • normal-erase-is-backspace-setup-frame
  • tty-register-default-colors

この辺に関係する設定をすれば良いでしょう。

またC言語側のコードは先に読まれるはずです。 GC関係のコードはsrc/alloc.cに記述されているので、 gc-cons-thresholdearly-init.el に書く方が良いです。

余談ですが、EXWM環境の場合 (setq frame-inhibit-implied-resize t) をするとEXWMがwindow resizeできなくなるので描画がおかしくなるので注意が必要です。

Compile時処理

el-get-bundleをeval-when-compile時に落とす

私は el-get ユーザーなので別のpackage managerのことはわかりませんが、package installはbyte-compile時に行っています。 dimitri/el-getInstallation を参考に設定していきます。

eval-when-compile はbyte-compile時にしか発行せず、生成された elc には処理結果が記述されるというものです。

私は el-get で380個程度のpackageを落としている関係上、非常に時間がかかるのでshallow cloneするようにしています。

(eval-when-compile
  (add-to-list 'load-path (locate-user-emacs-file "el-get/el-get"))
  (with-current-buffer
      (url-retrieve-synchronously
       "https://raw.githubusercontent.com/dimitri/el-get/master/el-get-install.el")
    (goto-char (point-max))
    (eval-print-last-sexp))

  (with-eval-after-load 'el-get-git
    (setq el-get-git-shallow-clone t)))

実際にinstallするpackageは以下のように記述しています。

(eval-when-compile
  (el-get-bundle "yasnippet"))

(eval-when-compile
  (el-get-bundle takeokunn/yasnippet-org))

環境ごとのif文をmacroで定義する

私のEmacs環境は3つあります。

  • Mac CLI環境
  • Mac GUI環境
  • Guix exwm環境

前提にも書いた通り、今回高速化するにあたって「Mac CLI環境」にフォーカスして話していたが、実際運用している環境は3つあります。 たとえば「Mac環境ではexwm関係のpluginは不要」のような、環境ごとに必要な処理やライブラリが微妙に違うので条件分岐が必要になってきます。

3環境を分岐できるようなmacroを作成し、byte-compile時に条件分岐しました。

;;; Mac CLI環境
(defmacro when-darwin (&rest body)
  (when (string= system-type "darwin")
    `(progn ,@body)))

;;; Mac GUI環境
(defmacro when-darwin-not-window-system (&rest body)
  (when (and (string= system-type "darwin")
             window-system)
    `(progn ,@body)))

;;; Guix exwm環境
(defmacro when-guix (&rest body)
  (when (string= system-type "guix")
    `(progn ,@body)))

ライブラリ周りの読み込み

async loadをする

擬似非同期ロードによる"待たされ感"改善 - Emacs の起動時間を"“詰める”" について。

run-with-timer で起動n秒後にqueue内の処理を順次実行するというアプローチです。

私はpackageを380個程度入れているので、起動した瞬間に使いたいpackageがなかなかdequeueしてくれないという問題が出てきました。 早く読まれてほしいpackageが以下です。

  • dash.elやs.elのようなbasic packages
  • amx
  • magit
  • ddskk
  • projectile
  • swiper/ivy/counsel
  • doom

普通にEmacsを起動した時最初にたたくコマンドは projectile であることや、 EDITOR=emacs git commit で立ち上がった時さっさと日本語入力できるようにしたいものです。 また、fish shellから M-g でmagitを起動できるようしています。

function magit
    set -l git_root (git rev-parse --show-toplevel)
    emacs -nw --eval "
(progn
  (add-to-list 'load-path (locate-user-emacs-file \"el-get/dash\"))
  (add-to-list 'load-path (locate-user-emacs-file \"el-get/compat\"))
  (add-to-list 'load-path (locate-user-emacs-file \"el-get/transient/lisp\"))
  (add-to-list 'load-path (locate-user-emacs-file \"el-get/ghub/lisp\"))
  (add-to-list 'load-path (locate-user-emacs-file \"el-get/magit-pop\"))
  (add-to-list 'load-path (locate-user-emacs-file \"el-get/with-editor/lisp\"))
  (add-to-list 'load-path (locate-user-emacs-file \"el-get/magit/lisp\"))
  (require 'magit)
  (setq magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1) (magit-status \"$git_root\"))"
end


function fish_user_key_bindings
    bind \eg magit
end

元記事を参考に優先順位高いqueueを処理する機構も作りました。

(defvar my/delayed-priority-high-configurations '())
(defvar my/delayed-priority-high-configuration-timer nil)

(defvar my/delayed-priority-low-configurations '())
(defvar my/delayed-priority-low-configuration-timer nil)

(add-hook 'emacs-startup-hook
          (lambda ()
            (setq my/delayed-priority-high-configuration-timer
                  (run-with-timer
                   0.1 0.001
                   (lambda ()
                     (if my/delayed-priority-high-configurations
                         (let ((inhibit-message t))
                           (eval (pop my/delayed-priority-high-configurations)))
                       (progn
                         (cancel-timer my/delayed-priority-high-configuration-timer))))))
            (setq my/delayed-priority-low-configuration-timer
                  (run-with-timer
                   0.3 0.001
                   (lambda ()
                     (if my/delayed-priority-low-configurations
                         (let ((inhibit-message t))
                           (eval (pop my/delayed-priority-low-configurations)))
                       (progn
                         (cancel-timer my/delayed-priority-low-configuration-timer))))))))

(defmacro with-delayed-execution-priority-high (&rest body)
  (declare (indent 0))
  `(setq my/delayed-priority-high-configurations
         (append my/delayed-priority-high-configurations ',body)))

(defmacro with-delayed-execution (&rest body)
  (declare (indent 0))
  `(setq my/delayed-priority-low-configurations
         (append my/delayed-priority-low-configurations ',body)))

autoload/with-eval-after-loadを活用する

autoload と with-eval-after-load - Emacs の起動時間を"“詰める”"について。

autoload の挙動は上記の記事に詳細に書かれているので省きます。 ただ、 autoload というのは1つの関数名しか引数に取れないので非常に不便です。 以下のような autoload-if-found という関数を作成ました。

(defun autoload-if-found (functions file &optional docstring interactive type)
  "set autoload iff. FILE has found."
  (when (locate-library file)
    (dolist (f functions)
      (autoload f file docstring interactive type))
    t))

使い方はこんな感じです。

(autoload-if-found '(lsp lsp-deferred) "lsp-mode" nil t)

with-eval-after-loadrequire が実行されたタイミングで読まれるものです。 autolaod-if-found ですべての処理を遅延している関係で、すべてのpackageに対して丁寧に指定する必要があります。 もし設定をしなければ、未定義変数になって起動時にWarningなりErrorが吐かれてしまいます。

以下の php-mode- の例のように、 with-eval-after-load には3種類の設定をするようにしています。

  • hook
  • keybind(map)
  • custom
(with-eval-after-load 'php-mode
  ;; hook
  (add-hook 'php-mode-hook #'lsp-deferred)

  ;; keybind
  (define-key php-mode-map (kbd "C-c C--") #'php-current-class)
  (define-key php-mode-map (kbd "C-c C-=") #'php-current-namespace)

  ;; config
  (setq php-mode-coding-style 'psr2))

設定

Magic File Name を一時的に無効にする

Magic File Name を一時的に無効にする - Emacs の起動時間を"“詰める”" について。

FileのI/Oは非常にコストがかかる行為だとあらためて感じました。

以下の記述を書くだけです。 書くだけでかなり改善するのでコスパの良い対応だと感じています。

;;; 行頭
(defconst my/saved-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)

;;; 行末
(setq file-name-handler-alist my/saved-file-name-handler-alist)

GCの設定

GC を減らす - Emacs の起動時間を"“詰める”"について。

起動時にGCが回ることはっきり言ってコストでしかないです。 起動時に一度もGCを回さない程度の大きさで設定しておくと良いです。

GCが回ったかどうかは前述の「評価のし方」で profile-report が出力してくれるので、そこで判断できるでしょう。 私は early-init.el に以下のように設定しています。

(setq gc-cons-threshold (* 128 1024 1024))

その他

add-to-listについて

安全な関数を諦める - Emacs の起動時間を"“詰める”" について。

add-to-list を使わずに push を使うほうが重複チェックを行わない関係で速くなるということが書かれています。

add-to-list はプログラミング言語の major-modemior-mode で設定する時に使うことが多いです。 私は数十のpackageを入れているのですが、 push に置き換えて事故って動かなくなったことがあります。

重複チェックをするかどうかで変わる秒数は1msよりも圧倒的に少ないだろうし、安全性を捨てるデメリットと比較してメリットが薄いように感じているので却下しました。

またasync loadしている関係で言語系の処理は遅延読み込みしているので、起動時には影響が出ないです。

Porテーブル Dumperについて

ポータブルダンパー - Emacs の起動時間を"“詰める”" について。

あらかじめpackageを読み込んでおいた状態のmemoryをdumpするしくみ。 今回の遅延評価をするアプローチでは Portable Dumper は活躍できませんでした。

記述量がほぼない状態のEmacs Lispファイルを用意してmemory dumpして読み込ませてみたところ、180ms程度かかりました。

そもそもNativeCompしているライブラリはdumpできなかったりといろいろな落とし穴があるらしく、非常に使いにくいものとなっています。 Emacsを自前Buildしたタイミングで生成されるdumpに自分のコードを埋め込むくらいすれば高速になりますが、別途用意をすると非常に遲くなります。

lsp-modeのperformanceについて

今回の起動時の高速化には関係ないが、 lsp-mode を高速化するTipsが公式サイトにあります。 私は lsp-mode をヘビーユーズしているので、パフォーマンスが大幅に改善されて生産性が上がりました。

Performance - lsp-mode

el-getのpackageもNativeCompする

これも今回の起動時の高速化には関係ないが、el-getで落としてきたpackageも一括でNativeCompする方が良いでしょう。 以下のような関数を用意し、あらかじめ実行しておくと良いです。

el-get/**/*.elelpa/**/*.el のすべてのファイルを再帰的にNativeCompするため時間がかかります。

(defun my/native-comp-packages ()
  (interactive)
  (native-compile-async "~/.emacs.d/init.el")
  (native-compile-async "~/.emacs.d/early-init.el")
  (native-compile-async "~/.emacs.d/el-get" 'recursively)
  (native-compile-async "~/.emacs.d/elpa" 'recursively))

終わりに

2022年はEmacsとひたすら向きあった1年でした。 今後10〜15年耐えられる設定とはなんなのかを考えた結果の1つに「起動時間の高速化」というのがありました。 ゼロから設定を見直し、より高速かつメンテナンス性の高い記述方法はなんなのか、 emacs.d はどうあるべきか、より善く生きていくためにはどうすれば良いのかを考えつくしました。

Emacs起動時間を高速化するにあたって、Emacs本体のソースコードを読む機会が増えて多くの知識を得ることができました。 ぜひ皆さんも自分の設定をあらためて見直すきっかけにしていただけると幸いです。

いつもTwitterで疑問に答えてくれるEmacs Hackerの皆さんのおかげでこの記事を書くことができました。 今後ともよろしくお願いします。