激进的emacs启动优化:跳过autoloads

抛砖引玉,我先说一下我自己了解的东西:

emacs的启动大致可以分为以下三个阶段:

  1. 加载early-init.el
  2. 初始化emacs,这里会自动调用到(package-initialize)
  3. 加载init.el

理论上:

  • 第一个阶段几乎不会产生时延
  • 第二个阶段产生的时延是一个很小的常量
  • 第三个阶段产生的绝大多数时延是可以推迟的

然而,当包的数量增加的时候,第二个阶段会产生比我们设想的常量大得多的时延。

这一时延的来源是:在这个阶段,由package.el管理的包在(package-initialize)的时候会被依次注册并激活。激活这个动作包括两个核心步骤:

  1. 把包的路径加入load-path
  2. 加载*-autoloads.elc(现在实际上是loaddefs)

不难看出,如果我们有几百个包待加载,那么这里的第二步将会依次加载几百个autoloads文件,会消耗大量的时间。即使我们在init.el里面什么都不做,只要我们装的包足够多,启动速度就会下降,这一现象在机械硬盘和虚拟机上会更显著。

如果我们不使用package.el,比如说用straight.el替代package.el,那么就可以跳过第二个阶段的(package-initialize)。所以相同的配置下,使用straight.el启动往往会更快一些。

这也是为什么你第二次启动emacs的速度会比第一次快一些,因为此时内存中第一次加载的那些autoloads文件产生的cache还没有被释放。

或者,我们选择跳过(package-initialize)的激活这一步,但是仍然使用use-package来管理包,此时我们需要:

  1. 在early-init.el中调用(setq package-enable-at-startup nil),让emacs不要自动调用(package-initialize)
  2. 在init.el中调用(package-initialize t),此时所有的包被注册,但是不会被激活。
  3. 在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个包,因为我居住在中国大陆。

package.el 也可以把 autoloads 编进一个文件,package-quickstart 了解一下。

2 个赞

nice, 这正是我需要的。