Emacsを高速起動せよ #kansaiemacs

この前の関西Emacsで発表?した内容を完結させた感じのエントリです。この動画の説明です。

みんなEmacsを高速起動したいですよね。ぼくもしたいです。 今回はカーネルの視点からEmacsを高速起動しちゃうよ。

まず実験用に ~/nisehome をHOMEにしてmelpaのパッケージをほぼ全てインストールして、さらにほとんどのパッケージ内のelispをrequireします。すると、めっちゃ起動が遅くなってくれます。具体的に見てみると

$ echo 3 | sudo tee /proc/sys/vm/drop_caches
$ HOME=$HOME/nisehome emacs

で(ファイルキャッシュを全て落としてから)起動してやって (emacs-init-time) を見てやると "348.6 seconds" となってます。だいぶおそいですね。やばい感じです。

このままでは今回の高速起動の対象にはできないので、まず新しいnamespaceに閉じ込めて、VNCの中でEmacsを起動します。

criu のwikiページ VNC - CRIU から newns.c とシェルスクリプトをとってきて起動します。

# ./newns ./newvnc env HOME=/home/naota/nisehome DBUS_SYSTEM_BUS_ADDRESS=unix:/tmp/foo /usr/bin/emacs

ここで DBUS_SYSTEM_BUS_ADDRESS を存在しないダミーのファイルに設定することで、システムのdbus、すなわちnamespaceの外のプロセスとのunix socket接続を防止します。

"vncviewer localhost:5925" するとEmacsが立ち上がっていくのがわかります。

pstreeでnewvncのプロセスIDを確認し、このプロセスツリーをダンプします。 lsすると大量にファイルができているのがわかります。ダンプされたデータです。 さらに pstree|grep vnc してもなにも出てきません。さっきあった VNCやEmacsのプロセスは全てダンプされて、停止されています。

# pstree -p|grep Xvnc
        |-newvnc(15079)-+-Xvnc(15080)-+-{Xvnc}(15083)
        |               |             |-{Xvnc}(15084)
        |               |             |-{Xvnc}(15085)
        |               |             `-{Xvnc}(15086)
# mkdir emacs-dump; cd emacs-dump
# criu dump -t 15079 --tcp-established
# ls
core-1.img    core-7.img         fdinfo-12.img      filelocks-49.img  ids-2.img        itimers-2.img      packetsk.img     pages-7.img           rlimit-103.img   sigacts-49.img    signal-p-5.img    tunfile.img
core-103.img  core-9.img         fdinfo-2.img       filelocks-9.img   ids-20.img       itimers-20.img     pagemap-1.img    pages-8.img           rlimit-106.img   sigacts-9.img     signal-p-6.img    unixsk.img
core-104.img  creds-1.img        fdinfo-3.img       fs-1.img          ids-23.img       itimers-23.img     pagemap-103.img  pages-9.img           rlimit-13.img    signal-p-1.img    signal-p-7.img    utsns-1.img
core-106.img  creds-103.img      fdinfo-4.img       fs-103.img        ids-49.img       itimers-49.img     pagemap-106.img  pipes-data.img        rlimit-14.img    signal-p-103.img  signal-p-9.img    vmas-1.img
core-107.img  creds-106.img      fdinfo-5.img       fs-106.img        ids-9.img        itimers-9.img      pagemap-13.img   pipes.img             rlimit-16.img    signal-p-104.img  signal-s-1.img    vmas-103.img
core-109.img  creds-13.img       fdinfo-6.img       fs-13.img         ifaddr-1.img     mm-1.img           pagemap-14.img   posix-timers-1.img    rlimit-2.img     signal-p-106.img  signal-s-103.img  vmas-106.img
core-13.img   creds-14.img       fdinfo-7.img       fs-14.img         inetsk.img       mm-103.img         pagemap-16.img   posix-timers-103.img  rlimit-20.img    signal-p-107.img  signal-s-106.img  vmas-13.img
core-14.img   creds-16.img       fdinfo-8.img       fs-16.img         inotify-wd.img   mm-106.img         pagemap-2.img    posix-timers-106.img  rlimit-23.img    signal-p-109.img  signal-s-13.img   vmas-14.img
core-16.img   creds-2.img        fdinfo-9.img       fs-2.img          inotify.img      mm-13.img          pagemap-20.img   posix-timers-13.img   rlimit-49.img    signal-p-13.img   signal-s-14.img   vmas-16.img
core-17.img   creds-20.img       fifo-data.img      fs-20.img         inventory.img    mm-14.img          pagemap-23.img   posix-timers-14.img   rlimit-9.img     signal-p-14.img   signal-s-16.img   vmas-2.img
core-19.img   creds-23.img       fifo.img           fs-23.img         ipcns-msg-1.img  mm-16.img          pagemap-49.img   posix-timers-16.img   route-1.img      signal-p-16.img   signal-s-2.img    vmas-20.img
core-2.img    creds-49.img       filelocks-1.img    fs-49.img         ipcns-sem-1.img  mm-2.img           pagemap-9.img    posix-timers-2.img    sigacts-1.img    signal-p-17.img   signal-s-20.img   vmas-23.img
core-20.img   creds-9.img        filelocks-103.img  fs-9.img          ipcns-shm-1.img  mm-20.img          pages-1.img      posix-timers-20.img   sigacts-103.img  signal-p-19.img   signal-s-23.img   vmas-49.img
core-21.img   eventfd.img        filelocks-106.img  ghost-file-1.img  ipcns-var-1.img  mm-23.img          pages-10.img     posix-timers-23.img   sigacts-106.img  signal-p-2.img    signal-s-49.img   vmas-9.img
core-23.img   eventpoll-tfd.img  filelocks-13.img   ids-1.img         itimers-1.img    mm-49.img          pages-11.img     posix-timers-49.img   sigacts-13.img   signal-p-20.img   signal-s-9.img
core-24.img   eventpoll.img      filelocks-14.img   ids-103.img       itimers-103.img  mm-9.img           pages-2.img      posix-timers-9.img    sigacts-14.img   signal-p-21.img   signalfd.img
core-4.img    fanotify-mark.img  filelocks-16.img   ids-106.img       itimers-106.img  mountpoints-1.img  pages-3.img      pstree.img            sigacts-16.img   signal-p-23.img   sk-queues.img
core-49.img   fanotify.img       filelocks-2.img    ids-13.img        itimers-13.img   netdev-1.img       pages-4.img      reg-files.img         sigacts-2.img    signal-p-24.img   stats-dump
core-5.img    fdinfo-10.img      filelocks-20.img   ids-14.img        itimers-14.img   netlinksk.img      pages-5.img      remap-fpath.img       sigacts-20.img   signal-p-4.img    tty-info.img
core-6.img    fdinfo-11.img      filelocks-23.img   ids-16.img        itimers-16.img   ns-files.img       pages-6.img      rlimit-1.img          sigacts-23.img   signal-p-49.img   tty.img
# pstree -p|grep vnc

では、復活させてみましょう。 キャッシュを落としてから復活させても 1秒で復活しています。 これが終わればふたたび "vncviewer localhost:5925" でつなげば Emacsの画面が出てきます。 elispを全て読み終わった状態でdumpしているので、elispの解析やら実行やらが全てスキップされて高速化しているわけですね。

# echo 3 > /proc/sys/vm/drop_caches
# time criu restore --tcp-established -d
(00.071714)    107: Error (image.c:213): Unable to open vmas-107.img: No such file or directory

real    0m1.240s
user    0m0.000s
sys     0m0.010s

ここで、つないだ VNC で C-x C-c します。当然 Emacsと VNCが終了します。 ではもう一度 restore して vncviewer でつないでみましょう。

# time criu restore --tcp-established -d
(00.065680)    107: Error (image.c:213): Unable to open vmas-107.img: No such file or directory

real    0m0.422s
user    0m0.000s
sys     0m0.010s

ふたたび Emacsが VNCで動いているのが見てとれるかと思います。サイコーですね。 Emacsは死なぬ! 何度でもよみがえるさ! ってやつです。



最後に背景について見ていきましょう。 今回使ったのは CRIU (checkpoint/restore in userspace)というものです。 これはプロセスの状態をディスクにダンプし、そのダンプデータからプロセスを再開するものです。

通常はXへの描画などをしていると Xと Emacs間の通信が切れてしまうのでX上で動く Emacsをダンプすることはできません。しかし、EmacsをVNC上で起動し、さらに1つの独立した namespace上でVNC(とEmacs)を起動します。 これでVNC下のプロセスをまとめてダンプできるようになります。

criuコマンドを使うにはカーネル3.11以上が必要です。さらに、いくつかカーネルの設定が必要です。 以下のものを有効にしておいてください。

  • CHECKPOINT_RESTORE
  • FHANDLE
  • EVENTFD
  • EPOLL
  • INOTIFY_USER
  • IA32_EMULATION
  • UNIX_DIAG
  • INET_DIAG
  • INET_UDP_DIAG
  • PACKET_DIAG
  • NETLINK_DIAG

criuコマンドは気のきいたディストリビューションならパッケージがあるかと思います。 Gentooにはありますし、emerge時に以上のカーネル設定もチェックしてくれるので間違いがありません。 お使いのディストリビューションの気がきいていなければ、 Gentoo をいれてみるのもいいでしょう。これから寒くもなりますし。

criuコマンドが入ったら"criu check"もしておきましょう。基本的なチェックが走ります。あとはおまけですが iptablesとか tunとかもいれとくといいです。 TCP接続を保ったままダンプできたりしてクールです。