速度突破裸配置/emacs -Q的极限:emacs启动时间只够你眨眼?

网上流传这么一个段子:emcas启动过程中可以煮一壶咖啡。(程序员在打开emacs的1-2分钟的碎片时间,可以干些什么?

对于圣战结束后对比过vim和emacs才入坑emacs的我来说,不管是365天开1次emacs,还是1天开365次emacs,都无法忍受emacs比其他同类慢。所以在最近重写init-file的过程中尤其注重各种包的配置对emacs启动时间的影响。

电脑:2016年的macbook,macOS Big Sur 11.5.1

emacs:emacs 27.2 安装方法: brew install emacs-plus --with-no-titlebar --with-modern-vscode-icon

包管理:use-package & straight.el

参考文章:

use-package中文翻译文档

完全摒弃package.el的配置,用use-package和straight.el管理所有的包

无限接近裸配置启动时间的Emacs配置思路

关于加快emacs启动速度(大家猪年好)

优化Emacs启动时间

Emacs 启动时间优化实践

减少emacs启动时间的高级技术

2个鲜为人知的提高Emacs启动速度的步骤

于是,不断的精简配置和结构化配置,不再需要package.el的一堆累述,全部的package通过straight自动从github上下载,melpa没有的包也能通过统一化的配置来安装:

(use-package XX
  :straight ....)

为了高度统一,那些build-in的包或者自定义的defualt也由use-package管理,例如:

(use-package frame
  :straight (:type built-in)
  :hook (before-make-frame-hook . window-divider-mode)
  :custom
  (window-divider-default-right-width     12)

每添加一组(use-package XX)配置都会写上“延迟加载”,然后重启一下emacs,看看M-x emacs-init-time用时多少,尽量避免写了过多配置而不知不觉中拖慢启动时间,尤其是新手抄作业很容易不小心拖慢启动时间。

就我个人环境而言,emacs -Q的时间为0.34-0.35秒

起初,early-init.el 只添加gc垃圾回收相关配置和(setq package-enable-at-startup nil),init.el 只添加use-package + straight初始化配置。这样最精简的配置也增加了0.2秒的时间,总用时0.52左右。后续添加了theme相关的配置,包括对defualt窗口之类的设置,虽然都:hook (window-setup . XX)了,但还是要花费0.02秒,总用时波动在0.54-0.56秒。

由此可见,即使add-hook了,还是会随着package的增多而增加启动时间,从几毫秒到几十毫秒不等。在不断的add-hook 和大致了解了Emacs 启动过程后,我突发奇想:为何不给整个init.el从头到脚加一个add-hook 呢?

于是整个init.el就变成了这样子的:

第二行;;;;;;;;;;;;;;下面就是各个(use-package XX)的配置和(add-hook(lambda的两个))。神奇的事情也随之发生,为了单一对比& emacs -Q,遂从终端( & emacs)启动emacs,启动时间瞬间缩小为0.30-0.31秒!

而通过alfred启动emacs,时间还要减少0.01秒左右:

经过多次以不同方式对比启动emacs,发现add-hook init.el后GUI emacs的启动时间确确实实突破了emacs -Q的时间。理论上emacs -Q的时间是GUI emacs的最快时间,着实惊讶到我了。真!在眨眼的瞬间启动图形化emacs!

总结与疑惑:

1、平时加载一些包会hook在after-init-hook,却不会影响emacs启动时间。从上图得知,只要加载的东西放到after-init-hook后就不会影响启动时间(实践中还是会有一点点耗时)。而init-file在after-init-hook前,如果init-file配置杂乱,没做好“按需加载”,很容易拖慢启动,所以我把init-file当作一个函数hook在after-init-hook后。init-file和after-init-hook后的其他东西一起加载,却又互补干扰。

2、虽然没有出错,但是对整个init-file进行add-hook,不知道有没有潜在的风险?如果没有,那每个包就无需再设置延迟加载。从启动速度这个角度来说,似乎with-eval-after-load :defer autoload等延迟技术就没多大意义了。

3、add-hook后可以接after-init-hook或者emacs-startup-hook,貌似前者要快一丢丢,而接window-setup-hook不能加载主题。

4、目前我的配置还很少,除了straight初始化和主题,其他的包还没配置上去,还需要继续验证其可行性和容错性。

6赞

1.gc

2.autoload

3.file-name-handler-alist

4.run-with-idle-timer

Emacs ready in 0.46 seconds with 1 garbage collections.

使用after-init-hook的话好像只是加快gui界面弹出的速度吧?弹出界面后不能操作,实际上启动时间还是没变,就很像vscode那样,先弹出一个窗口再慢慢加载,给人一种很快的感觉。延迟加载对于emacs来说意义还是很大的,emacs还是单线程的,不管怎么优化想让所有包都加载完成,耗费时间总和还是不变的,延迟加载主要还是通过按需加载,分散这些包的加载时间,这样启动时间才会真正减少。

impressed :+1:

首先是对“emacs启动时间”的计算,我看大家一般都是用emacs-init-time,我也用了这个作为指标。从这指标的数值看确实是加快了。至于弹出界面后能否操作,我尝试了一下,直接双击el文件打开emacs,以最快的速度按空格键,是可以输入空格的,没感觉到卡顿。

我的包都用了:hook钩在window-setup-hook上,这是emacs启动后及界面显示前的最后一个钩子,当界面显示时,我的包也在加载或加载完成,所以可以进行操作。

这篇文章可以看出,init-file是在after-init-hook前加载,如果init-file配置杂乱就会导致启动慢,因此我把整个init-file当作一个函数钩在after-init-hook后面,这样就相当于没有init-file,而after-init-hook运行后,界面显示和我的init-file同时加载却又不影响前者(这点挺神奇的)。这样看来,emacs-init-time应该大于或等于 emacs -Q的时间才对,我的反而少于 emacs -Q的时间,可能是因为我的配置少或者是bug。后续继续观察一下。

我使用的是emacs 28,我的 -Q 是0.18s,测试了一下,这个是不使用 after-init-hook 的结果:

这个是使用延迟加载并使用了楼主提到的 after-init-hook 的结果: 这个是不使用延迟加载加上使用 after-init-hook 的结果: 使用 after-init-hook 提升好像比较有限,关了延迟加载后明明响应的最慢,emacs-init-time 反而还是最少的 :joy: 。或许楼主可以多装点包测试一下?

5秒到4秒,少一秒也是爱呀,虽然没有少到emacs -Q :rofl:

嗯……emacs-init-time这个时间确实值得细细考虑……

对,为了严谨,应该继续添加包配置看看效果如何。我后续会继续更新反馈滴~

你这个应该还不够极限,还有很大的提升空间。 我觉得emacs的极限是只比neovim慢一点点。 我的台式也很老了(i7-6700)。 2021-09-13_22-51_1

1赞

:scream:怎么可以这么快?这就是linux的威力吗?还是用了什么黑科技?dump了?我这里emacs -Q -nw都要0.055了……

不好意思, 我忘记了我这个启动速度是先开daemon再开client的emacs-init-time. 所以实际上没这么夸张。 emacs -Q: 2021-09-13_23-30_1

emacs -nw -Q: 2021-09-13_23-40

emacs -nw (核心配置 有些包在终端不好用)

2021-09-13_23-42_1

emacs (核心配置+GUI)

2021-09-13_23-45

emacs --daemon; emacsclient -cn (也就是我实际上的启动方式 完整配置) 2021-09-13_23-50

环境:arch, emacs-28 with native-compilation,无dump

至于为什么比刚刚的0.093还要快,那是因为我读了你发的那个链接 用了 (let ((file-name-handler-alist nil)) (…)) 还真的有效。 应该还能比这更快,比如dump我就没研究过。

我的配置在:https://github.com/alexluigit/emacs-grandview

借鉴了doom-emacs的一些东西,也有一些挺难看的技巧。但我总之就是想让启动速度是neovim那个水平。

一篇对我启发挺大的贴子 (doom-emacs 的作者讲解为什么doom启动很快) https://www.reddit.com/r/emacs/comments/f3ed3r/how_is_doom_emacs_so_damn_fast/

2赞

帅的帅的,时间压的很好啊,只比-Q多0.03秒。谢谢分享,我也学习学习。

好像加了-Q以后,dashboard啥的都没了。。。。各种不习惯,感觉为了启动速度,干掉一些习惯的东西得不偿失。。。

对啊,加Q就是忽略配置,跳过dashboard或splash,直接进入scratch的buffer。加Q只是为了看看gui emacs的最快启动速度。

我的做法是把所有的配置当作一个函数hook到after-init-hook上,这不会影响你的配置,也就是不会影响操作习惯。

建议使用pdump…autoload太自己骗自己了…

https://archive.casouri.cat/note/2020/painless-transition-to-portable-dumper/index.html

只有emacs28 nativecomp 不能用

我也留意到了,等我用常规方法配置好以后就试试这黑科技 :v:

补充下,光是看emacs-init-time不科学。Centaur中是尽量把能放after-init的包都加到after-init-hook中启动,但不是所有,得看实际情况,否则只是看到了UI并不能实际使用该功能。doom和Centuar中使用了各种技巧加快启动速度,但是最快的方式还是使用daemon或者server-mode,然后用emacsclient。冷启动怎么也不可能超过 emacs -Q 和 vim (其实vim包多了一样可能会慢)。总结下,功能和性能是一个平衡的艺术,减少功能速度就会加快,增加需要的功能并达到预期可接受的速度就很完美。目前为止,延迟加载是emacs的yyds,native-comp 和 dump 副作用都挺大。。。

1赞

想问问你从alfred启动emacs的script长啥样?

我觉得无非就是 bash 或 applescript.

推广一下我写的

https://github.com/willbchang/alfred-open-in-editor

棒!你这个是编辑器大集成啊 我用chemacs管理好几套不同的emacs配置,需求是输入doom就启动doom-emacs,输入space就启动spacemacs ,诸如此类。 所以目测还得自己倒腾一番…

1赞

好奇,请问一下 native-comp 和 dump 有啥副作用?