🏃‍♂️

Emacsを0.5msで起動させた

に公開

前段

半年前から腰を据えてプログラミングをしようと決めて、紆余曲折あって、最近emacsに触れ始めました(初心者マーク)。以前はneovimを扱っていて、否が応でも比較してしまい、如何せん起動の遅さが気になった。それ以外の不満点は私の設定不足でしかないので気になりません。なので、とにかく起動を高速にするために調べ始めました。

するとemacsclientで起動速度を根本から解決するのが良さそうという雰囲気を感じ取った。しかし直感的に気に入らなかったので、逐一起動させて起動速度を高速にする方法に固執して試行錯誤することにした。 そして今記事では「私が高速だと思った起動方法と設定」を示します。

結論

全体を遅延させた設定ファイルを

emacs -Q -l ~/.emacs.d/設定.elc <好きなテキストファイル>

で呼び出す。

説明

簡単に言うと、起動と設定の処理をくっきり分けます。まず起動を行って、次に遅延された設定を処理させるので、どれだけパッケージを入れても起動速度が保たれる、、、はず。

1.コマンドの説明

emacs -Qで起動させると、init.elなど諸々が読み込まれず起動します。

0.000440 seconds、─────風。


次に空のinit.elを置いて、emacsで起動します。

0.194558 seconds、日が暮れるのかと思いました。

この比較から分かるのは、init.elを工夫しても、普通に起動すると200ms掛かる。
そして最低限なら0.5msほどで起動できるということです。0.5ms待てば少なくとも編集はできるのに、記憶のない設定を読み込む時間によって、0.5msから200ms、つまり400倍の時間を待たされている。

-lはロードするファイルを指定します。

時間の計測はemacs-init-timeで行っていますよ。

2.設定ファイルの説明

まず、ファイル名は直接指定するので、設定ファイルがバイトコンパイルされてさえいれば問題ありません。

次に、設定の内容についてです。
これは画面の表示と設定の読み込みが並行すると画面の表示が遅れるので、画面が表示されるまで全体を遅延させます。

これを怠ると、画面が表示されて編集できるようになるまで100msとか待たされます。処理しながら起動するのと、起動した後に処理するので致命的な差分がある。

実践

論より証拠ということで
https://gist.github.com/aosjmi/5acec0d1f1804e6e47586eb5bf5c6efd
まだよくわからないけど使いそうなのを入れてみました。起動速度にしか特化していません。

evil
rainbow-mode
proof-general
elpy
hindent
ag
qml-mode
racket-mode
php-mode
go-mode
kotlin-mode
nginx-mode
toml-mode
love-minor-mode
dockerfile-mode
nix-mode
purescript-mode
markdown-mode
jinja2-mode
nim-mode
csharp-mode
rust-mode
cmake-mode
clojure-mode
graphviz-dot-mode
lua-mode
tuareg
glsl-mode
yaml-mode
d-mode
scala-mode
move-text
nasm-mode
editorconfig
tide
company
powershell
js2-mode
yasnippet
helm-ls-git
helm-git-grep
helm-cmd-t
helm
multiple-cursors
magit
haskell-mode
paredit
ido-completing-read+
smex
gruber-darker-theme
org-cliplink
dash-functional
dash

これをコマンドで指定するパスに配置する(今回は~/.emacs.d/下)

次に再帰的にバイトコンパイルします

emacs -batch -f batch-byte-compile <path>

起動します

emacs -Q -l ~/.emacs.d/init.elc <好きなテキストファイル>

0.000437 seconds

無事に高速で起動できました。evilがいつ起動したか確かめるために、起動後即座にjを長押ししたところ、一文字jが入力されて、あとは移動キーになりました。

今回の環境なら0.0005s前後で起動できるので、0.001sの遅延が意味を成してるのが分かると思う。(こういうのは公開すべきでないね)

(add-hook 'emacs-startup-hook
  (lambda ()
    (run-with-idle-timer 0.001 nil #'load-theme)
    (run-with-idle-timer 0.002 nil #'actual-init)))

例外として、最初にテーマと同じ配色を軽量な形で挟むことによって画面のちらつきを防いで、驚きを減らしてる。試行錯誤の最初の頃は起動が10msでちらつきが目立っていたから意味があったのですが、今は必要ないかもしれません。

(let (  (gruber-darker-fg "#e4e4ef")               
	(gruber-darker-fg+1      "#f4f4ff")
	(gruber-darker-fg+2      "#f5f5f5")
	(gruber-darker-white     "#ffffff") 
	(gruber-darker-black     "#000000")
	(gruber-darker-bg-1      "#101010")
	(gruber-darker-bg "#181818") 
	(gruber-darker-bg+1      "#282828")
	(gruber-darker-bg+2      "#453d41")
	(gruber-darker-bg+3      "#484848")
	(gruber-darker-bg+4      "#52494e") 
	(gruber-darker-red-1     "#c73c3f") 
	(gruber-darker-red"#f43841")       
	(gruber-darker-red+1     "#ff4f58") 
	(gruber-darker-green     "#73c936")   
	(gruber-darker-yellow    "#ffdd33")   
	(gruber-darker-brown     "#cc8c3c")    
	(gruber-darker-quartz    "#95a99f")    
	(gruber-darker-niagara-2 "#303540")    
	(gruber-darker-niagara-1 "#565f73")       
	(gruber-darker-niagara   "#96a6c8")  
	(gruber-darker-wisteria  "#9e95c7"))   
	(set-background-color gruber-darker-bg) 
	(set-foreground-color gruber-darker-fg)  
	(set-face-attribute 'default nil :background gruber-darker-bg :foreground gruber-darker-fg)                 
	(set-face-attribute 'cursor nil :background gruber-darker-yellow)  
	(set-face-attribute 'region nil :background gruber-darker-bg+2 :foreground gruber-darker-white)             
	(set-face-attribute 'highlight nil :background gruber-darker-bg+1)
	(set-face-attribute 'fringe nil :background gruber-darker-bg) 
	(set-face-attribute 'font-lock-comment-face nil :foreground gruber-darker-brown)                            
	(set-face-attribute 'font-lock-string-face nil :foreground gruber-darker-green)                             
	(set-face-attribute 'font-lock-function-name-face nil :foreground gruber-darker-niagara)                    
	(set-face-attribute 'font-lock-keyword-face nil :foreground gruber-darker-yellow :weight 'bold)             
	(set-face-attribute 'font-lock-variable-name-face nil :foreground gruber-darker-quartz)                     
	(set-face-attribute 'font-lock-type-face nil :foreground gruber-darker-wisteria)                            
	(set-face-attribute 'mode-line nil :background gruber-darker-bg+1 :foreground gruber-darker-white)          
	(set-face-attribute 'mode-line-inactive nil :background gruber-darker-bg-1 :foreground gruber-darker-quartz)
	(set-face-attribute 'font-lock-builtin-face nil :foreground gruber-darker-yellow)                           
	(set-face-attribute 'font-lock-constant-face nil :foreground gruber-darker-wisteria)
)    

まとめ

起動が完了されるまでの刹那0.5ms、設定の処理を待つだけで起動速度が大幅に改善された。
同じことをしてる人がいなかったため、現状は気にならないが、使っていくと致命的な問題が出るかもしれない。その時は素直にemacsclientを使おうと思います。

初めてzennで記事を書きました。最後までご覧いただきありがとうございます。

Discussion