抛砖引玉,我先说一下我自己了解的东西:
emacs的启动大致可以分为以下三个阶段:
- 加载
early-init.el
- 初始化
emacs
,这里会自动调用到(package-initialize)
- 加载
init.el
理论上:
- 第一个阶段几乎不会产生时延
- 第二个阶段产生的时延是一个很小的常量
- 第三个阶段产生的绝大多数时延是可以推迟的
然而,当包的数量增加的时候,第二个阶段会产生比我们设想的常量大得多的时延。
这一时延的来源是:在这个阶段,由package.el
管理的包在(package-initialize)
的时候会被依次注册并激活。激活这个动作包括两个核心步骤:
- 把包的路径加入
load-path
- 加载
*-autoloads.elc
(现在实际上是loaddefs
)
不难看出,如果我们有几百个包待加载,那么这里的第二步将会依次加载几百个autoloads文件,会消耗大量的时间。即使我们在init.el
里面什么都不做,只要我们装的包足够多,启动速度就会下降,这一现象在机械硬盘和虚拟机上会更显著。
如果我们不使用package.el
,比如说用straight.el
替代package.el
,那么就可以跳过第二个阶段的(package-initialize)
。所以相同的配置下,使用straight.el
启动往往会更快一些。
这也是为什么你第二次启动emacs的速度会比第一次快一些,因为此时内存中第一次加载的那些autoloads文件产生的cache还没有被释放。
或者,我们选择跳过(package-initialize)
的激活这一步,但是仍然使用use-package
来管理包,此时我们需要:
- 在early-init.el中调用
(setq package-enable-at-startup nil)
,让emacs不要自动调用(package-initialize)
- 在init.el中调用
(package-initialize t)
,此时所有的包被注册,但是不会被激活。 - 在init.el中手动将安装的包路径加入
load-path
,这只需要一个循环就可以完成。
注意,这种策略下所有预设的autoloads以及子路径都会失效,这意味着你需要自己决定所有需要自动加载的函数(使用use-package
的:autoload
和:commands
关键字创建对应的autoload语句),原来的:init
大概率会失效。
这同样是一笔很不划算的买卖,毕竟,即使你像我一样装了200个包,在linux上每次启动时加载autoloads产生的时延也就0.5秒左右,这值得吗?(在windows上一般而言这个时延会更大)。
use-package
似乎也没有希望你这么使用,就我所知,它并没有提供一个诸如:activate
的关键字控制何时激活一个包,读取*-autoloads.elc
。它只会在一个包满足给定加载条件的时候直接require
而已。实际要做到这一点可能并不难,只需要使用package.el
内部的package-activate
接口。
straight.el
做了一个很聪明的决定,那就是把这些autoloads统统编进一个文件。但是它在依赖关系的处理上就并不是那么聪明了。这同样是一个tradeoff,你得忍受straight.el的缺点。对我来说,我无法忍受在部署配置时从github拉取200个包,因为我居住在中国大陆。