前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Emacs 启动时间优化实践

Emacs 启动时间优化实践

作者头像
飞驰的西瓜
发布2022-07-26 16:31:02
9170
发布2022-07-26 16:31:02
举报
文章被收录于专栏:EmacsTalk

最近在知乎上回答了一个问题请问你的emacs启动需要多久?[1],之前一直没怎么花精力去优化启动时间,虽然知道一些理论,但纸上得来终觉浅,于是动手实践。截至发表本文前,优化后的配置运行了三周多,体验和之前无异。

目标

Emacs 中提供了一个函数来记录启动时长,即 emacs-init-time ,后文也用这个时间作为优化目标。

代码语言:javascript
复制
emacs -Q -nw
M-x emacs-init-time
0.005363 seconds

零配置下启动时间,是优化的终极值。

效果

介绍具体步骤前,先看看这次优化的数据

阶段

(length package-alist)

(emacs-init-time)

优化前

116

6.50532

优化后

65

0.943392

工具

use-package

我现在的配置都是基于 use-package 来配置,use-package 提供了下面两个配置项来打印包的加载时间:

use-package-verbose ,设置为 t 即可打印包加载的信息

use-package-minimum-reported-time ,超过这个设定时间会打印耗时,默认是 0.1 秒

有一点需要注意,verbose 统计的是 :config 内的执行时间, :init 的不会统计,所以这个方式统计的时间不一定准确。

benchmark-init-el[2]

本次优化主要使用这个工具,它提供了两种视图:

benchmark-init/show-durations-tabulated 表视图,可以查看一个包以及其依赖的加载时间

代码语言:javascript
复制
| Module                       |  Type   | ms [^] | total ms |
+------------------------------+---------+--------+----------+
| org-contacts                 | require |    10  |      1164|

上面这个表示加载 org-contacts 本身需要 10ms ,但是加上其依赖后,总耗时却要 1164ms ,说明其依赖非常重。

benchmark-init/show-durations-tree 树视图,可以查看包的加载顺序,

╼►[benchmark-init/root nil 6987ms] ├─[xdg require 14ms] │ ├─[~/.config/emacs/var/autoloads/elfeed-autoloads.el load 46ms] │ ├─[evil-terminal-cursor-changer require 4ms] │ ├─[org-contacts require 10ms] │ │ ├─[org-capture require 17ms] │ │ ├─[org-agenda require 29ms] │ │ │ ╰─[org-refile require 18ms] │ │ ├─[gnus-art require 27ms] │ │ │ ├─[mm-uu require 15ms] │ │ │ │ ╰─[mml2015 require 15ms] │ │ │ ├─[mm-view require 15ms] │ │ │ │ ├─[mml-smime require 15ms] │ │ │ │ ╰─[smime require 21ms] │ │ │ │ ╰─[dig require 14ms] │ │ │ ├─[gnus-sum require 31ms] │ │ │ │ ├─[url require 13ms] │ │ │ │ │ ├─[url-proxy require 17ms] │ │ │ │ │ ├─[url-privacy require 13ms] │ │ │ │ │ ├─[url-expand require 13ms] │ │ │ │ │ │ ╰─[url-methods require 13ms] │ │ │ │ │ ├─[url-history require 17ms] │ │ │ │ │ ╰─[mailcap require 17ms] ...... ......

通过上面的树状图,可以看到 org-contacts 所有依赖的加载时间。本次优化前的数据放在这个 gist[3] 中,供读者参考。

自定义 timer

代码语言:javascript
复制
(defmacro my/timer (&rest body)
  "Measure and return the time it takes evaluating BODY."
  `(let ((time (current-time)))
     ,@body
     (float-time (time-since time))))

通过 my/timer 这个宏,可以很方便的测试某段代码的执行时间。

指导思想

• 尽可能懒加载包

• 精简配置,去掉那些华而不实的包,之前很有可能一时兴起安装的包,但是之后再也没用过

优化过程

懒加载所有包

大多数包的安装说明中,都会推荐通过 (xxx-mode 1) 的方式来开启该 mode,这样的优势是简单,用户出问题的机率小,但是带来的一个问题就是会在 Emacs 启动时去加载这些包,即使暂时用不到它。

use-package 提供了 :defer 关键字来支持懒加载,取值如下:

t ,表示不会主动加载这个包

• 数字,表示延迟多少秒后加载,内部用 run-with-idle-timer 实现

优化后的配置大部分包均有 :defer t ,然后通过 hook/autoloads 的方式来懒加载,对于其他一些重点需要的包,通过设置延迟时间来优化。比如:

• evil/evil-leader/smex 为 2

• autorevert/so-long/window-numbering 为 5

通过这一步,可以 极大 减少启动时间,也是本次优化最为耗时的部分。在进行实践时,可以通过 benchmark-init 的表视图,找到加载最耗时的包,然后逐个优化。

精简配置

在进行第一步的过程中,发现 projectile 这个包需要 0.7s 的时间,主要时间耗在了 (projectile-mode 1) 这一句上。

代码语言:javascript
复制
(my/timer
 (use-package projectile
   :init
   (projectile-mode +1)
   :bind ("C-c p" . projectile-command-map)
   :custom (projectile-project-search-path '("~/code/" "~/gh/" "~/code/antfin/" "~/code/misc"))
   :config
   (setq projectile-switch-project-action #'projectile-find-file-dwim
         projectile-completion-system 'ivy
         ;; projectile-enable-caching t
         projectile-project-root-functions '(projectile-root-local
                                             projectile-root-bottom-up)
         projectile-project-root-files-bottom-up '(".projectile" "README.org" "README.md"
                                                   "Makefile" "pom.xml" "go.mod" "cargo.toml" "project.clj"
                                                   ".git" ".hg")
         projectile-ignored-project-function (lambda (project-root)
                                               (cl-dolist (deny '("\\.git" "\\.rustup" "\\.cargo" "go/pkg" "vendor"))
                                                 (when (string-match-p deny project-root)
                                                   (cl-return t)))))))
;; 0.781213

我日常工作流重要依赖项目管理,具体来说有以下三点:

1. 可以方便的切换 project

2. 可以方便的自定义 project-root ,对于 monorepo 来说尤为重要,而且 lsp-mode/citre 之类的工具也都依赖这个

3. project 内搜索文件要快

projectile 我也是调教了很久才用的比较舒服,但感觉还是太重,于是想看看能否用 Emacs 自带的 project.el 来替代它,通过一番搜索,发现 28 版本的 project.el 通过一些简单配置即可达到 projectile 同样的功能,于是果断去掉了 projectile 这个依赖。

目前我使用的 project.el 配置可参考:i-basic.el#L85-L166[4],对于 27 版本的用户,可以在 这里[5]下载最新的 project.el。这次去掉的其他华而不实的包主要还有:

• evil-numbers

• company-native-complete

• comment-dwim-2

• carbon-now-sh

• ob-http/ob-sql-mode/org-sidebar/org-bullets

• all-the-icons/all-the-icons-ivy/all-the-icons-dired

• calfw/cal-china-x

• easy-hugo

这些包的特点是:看上去很实用,但基本上没用过,去掉完全不影响使用体验。

重新组织配置文件

通过 benchmark-init 的数据来看,org 相关包占了很大一部分,通过 defer 可以把其相关配置懒加载,但是还有一点容易忽略,即 org-babel。优化前的配置是放在一个大 org 文件中,即所谓的『文学式编程』。

代码语言:javascript
复制
(org-babel-load-file (expand-file-name "core.org" user-emacs-directory))

优化后是拆分到多个 el 文件中,使用 load-file 来加载,之所以选择 load-file,而不是 require 之类的高级 API,是因为它比较底层,黑魔法会少一些。

代码语言:javascript
复制
(let ((file-name-handler-alist nil))
   (dolist (el '("i-basic.el"
                 "i-edit.el"
                 "i-prog.el"
                 "i-ui.el"
                 "i-misc.el"))
     (load-file (expand-file-name el user-emacs-directory))))

file-name-handler-alist 设置为 nil 是参考 2 easy little known steps to speed up Emacs start up time[6]

其他优化

下面列的一些方案本次优化前已经使用,仅供读者参考。以下代码在 early-init.el 中添加:

代码语言:javascript
复制
(setq gc-cons-threshold most-positive-fixnum)
(defvar my/gc-timer
  (run-with-idle-timer 30 t
                       (lambda ()
                         (let ((inhibit-read-only t)
                               (gc-msg (format "Garbage Collector has run for %.06fsec"
                                               (my/timer (garbage-collect)))))
                           (with-current-buffer "*Messages*"
                             (insert gc-msg "\n"))))))
;; 上面代码的含义是只在空闲时进行 GC ,最大程度避免 GC 停顿导致的卡顿
(setq read-process-output-max (* 1024 1024)) ;; 1mb
;; 从子进程一次读取的最大字节数,默认是 4K ,对于使用 JSON 通信的 LSP 协议来说,太小了
;; 调大这个值可以减少系统调用次数

总结

Emacs 的启动慢是个老生常谈的问题,但熟练用户重启 Emacs 的机率比较小,一般都是 server 模式常驻的,所以启动慢的问题对他们来说不严重,但是对于新手或其他编辑器阵营的用户来说,启动慢就是一个大瑕疵,希望通过本文的实践能给读者提供优化思路的同时,让更多读者喜欢上把玩 Emacs 。

参考

• It’s never too late[7]

• Advanced Techniques for Reducing Emacs Startup Time[8]

引用链接

[1] 请问你的emacs启动需要多久?: https://www.zhihu.com/question/472788138/answer/2006637253 [2] benchmark-init-el: https://github.com.cnpmjs.org/dholm/benchmark-init-el [3] 这个 gist: https://gist.github.com/jiacai2050/cf30db07bb2e95ffb7d5114bc95c0cfc [4] i-basic.el#L85-L166: https://github.com/jiacai2050/dotfiles/blob/4f32f740a7a793bbf12d1557fd4e0e60baf2381a/.config/emacs/i-basic.el#L85-L166 [5] 这里: https://elpa.gnu.org/packages/project.html [6] 2 easy little known steps to speed up Emacs start up time: https://www.reddit.com/r/emacs/comments/3kqt6e/2_easy_little_known_steps_to_speed_up_emacs_start/ [7] It’s never too late: https://www.manueluberti.eu/emacs/2020/09/18/project/ [8] Advanced Techniques for Reducing Emacs Startup Time: https://blog.d46.us/advanced-emacs-startup/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 EmacsTalk 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目标
  • 效果
  • 工具
    • use-package
      • benchmark-init-el[2]
        • 自定义 timer
        • 指导思想
        • 优化过程
          • 懒加载所有包
            • 精简配置
              • 重新组织配置文件
              • 其他优化
              • 总结
              • 参考
              相关产品与服务
              项目管理
              CODING 项目管理(CODING Project Management,CODING-PM)工具包含迭代管理、需求管理、任务管理、缺陷管理、文件/wiki 等功能,适用于研发团队进行项目管理或敏捷开发实践。结合敏捷研发理念,帮助您对产品进行迭代规划,让每个迭代中的需求、任务、缺陷无障碍沟通流转, 让项目开发过程风险可控,达到可持续性快速迭代。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档