zsh 启动速度慢的终极解决方案 - 知乎
2022-5-12 00:16:1 Author: zhuanlan.zhihu.com(查看原文) 阅读量:315 收藏

zsh 启动速度慢的终极解决方案

zsh 的交互式体验堪称是最强的——丰富的插件,强大的框架,将 zsh 的交互式体验推向了极致。然而另一方面,过多的插件,臃肿的主题,也让 zsh 变得反应迟钝,反过来破坏了交互式体验。

很多人反映的 zsh 的慢主要体现在两个方面:1. 启动速度,2. prompt 反应速度慢

影响第一点的主要是插件的数量和质量,影响第二点的则是主题。 因此不少人想到的第一个解决方案就是精简插件和主题。诚然,这是最有效的解决方案,但是这就和使用 zsh 的初衷相违背了:使用 zsh,不就是希望自己的 shell 能够用起来更方便么?

而且很不巧的是,zsh 社区最为著名的框架——oh my zsh(下称 OMZ),针对这两点都没有任何优化。不仅给 zsh 带来了龟速的名声,还让不少人由 OMZ 入门的用户最终都抛弃了 OMZ,转而使用插件管理器自行创建配置(也有人创建了在速度上进行了优化的框架如 Prezto、ZIM)。

然而这并不能解决问题。说到 zsh 的插件管理器,最著名的就是 antigen,可以说是 zsh 社区的事实标准。可是这个事实标在速度方面也没有进行过多优化,因此在插件慢慢变多以后,还是会发现 zsh 的启动时间变得越来越慢……

于是一大批插件管理器接连冒出,antibody,zgen,zplug……每个都宣称自己解决了问题,然而都不尽如人意。毕竟能采用的优化方式无非就是提前编译+缓存,再怎么优化都是有极限的。直到我发现了 zinit……(PS:zplug 的思路其实也很棒,但是实现实在是太挫了,反倒起了反作用)

注:zinit 原名 zplugin,写这篇文章的时候还没有改名,所以图片里都是 zplugin。

Zinit 有多快

第一眼看到 zinit 的时候,其实我是拒绝的,因为它的 GitHub repo 只有数百 star,我下意识觉得这不过又是一个没啥新意的轮子。直到我看到这篇文章,比较了各大 zsh 框架&插件管理器的速度,我才愕然发现,最不起眼的 zinit,竟然是最强悍的一个!

(上图 zinit 速度不降反增,可能是实验误差……)

看到这幅图,不少人肯定会觉得——这肯定是作弊了吧?怎么可能快?

我当初也是怎么想的,然而试用了以后,我发现——竟然真的这么快!而且插件数量越多 zinit 优势越大!

我的配置只需要平均 25ms 的时间就能启动。而且这可不是什么精简配置,而是包含了十多个插件和半个 OMZ 框架的中量级配置

zinit:我不是针对谁,我是说,除我以外,在座的各位,都是辣鸡!

为什么 zinit 会这么快?即使是预编译了插件,对外部命令的调用却是优化不了的(这也是拖慢启动速度的大头),这个速度和其他插件管理器比起来,简直就是降维打击!

秘密就在于它提供的一个独特的插件加载机制。

众所周知,尽管不少人会给 zsh 配上一堆插件,但很多插件都不是启动一个 shell 以后立马就需要使用的。 比如说 thefuck,这个插件只有在你打错的命令的时候才会被激活。然而为此你却需要在启动时花费数百毫秒来加载,这显然是一种浪费,为什么不能将这类插件的加载延迟到 zsh 启动以后呢?

zinit 就是在这个方向上的一次成功尝试。它提供了 “Turbo Mode”,允许延迟加载插件,一般来讲可以加速 50% 到 70%!

说了这么多,究竟该如何使用呢?

首先声明一下,我并不打算写一篇详尽的使用指南,因为我已经写过一篇。 然而回过头来看才发现这玩意儿实在是……又臭又长,里面讲的很多功能其实根本用不到……(尽管那篇文章声称自己只覆盖了 zinit 功能的冰山一角(没办法,zinit 功能实在是太多了))

因此这篇教程决定只覆盖最常用的命令,让人快速上手。如果看完文章以后你对 zinit 确实产生了兴趣,倒是可以阅读一下那篇文章。

安装

自动安装

刚正朴实的安装方式,官方推荐

sh -c "$(curl -fsSL https://raw.githubusercontent.com/zdharma/zinit/master/doc/install.sh)"

手动安装

没有诸如需要使用 dotfiles 管理配置的需求的话,不建议手动安装

首先 clone repo 到随便哪个位置

git clone https://github.com/zdharma/zinit.git ~/.zinit/bin

然后在你的 ~/.zshrc 顶端添加如下语句

source ~/.zinit/bin/zinit.zsh

现在,就可以在你的 ~/.zshrc 中使用 zinit 命令来加载插件了~

配置

从 GitHub repo 加载插件

最为常见的加载方式,和其他插件管理器的用法是一致的,直接 zinit light {用户名}/{repo名} 即可。

zinit light zsh-users/zsh-autosuggestions
zinit light zdharma/fast-syntax-highlighting
# PS. zinit 和 fast-syntax-highlighting 是同一个作者,这位作者对速度的追求确实让人钦佩

加载 oh my zsh 插件

OMZ 别的不说,插件是真的多,如果不能加载 OMZ 插件那就太遗憾了。因此像 antigen 这样的插件管理器甚至直接提供了整合 OMZ 框架的能力。

zinit 并没有直接整合 OMZ,不过提供了更为强大的 snippet 命令来加载单独的插件。

单文件插件

snippet 命令可以接 URI 来直接加载单个文件。比如加载 OMZ 的 sudo 插件:

zinit snippet OMZ::plugins/sudo/sudo.plugin.zsh

后面那部分看起来好像不像一个 URI?因为 OMZ 实在太常用了,因此可以用 OMZ:: 来代替它的 repo 地址(对于 Prezto 则是 PZT::),上面的写法其实等价于:

zinit snippet https://github.com/ohmyzsh/ohmyzsh/blob/master/plugins/sudo/sudo.plugin.zsh

多文件插件

有时候,插件目录里不止一个文件(比如包含插件+补全),这时就需要使用 svn 修饰词来加载了:

zinit ice svn
zinit snippet OMZ::plugins/extract

这里不详细解释什么叫修饰词了,你可以理解成一种实现可选参数的手段,修饰词只对接下来的一条命令起效。 格式是 zinit ice {修饰词1} {修饰词2} ...

svn 修饰词,表示下一行的 URI 需要使用 SVN 协议加载。此时 zinit 会使用 SVN 协议下载整个目录,自动识别并加载需要的文件。

加载补全

补全文件,有两种加载方式:1. 使用 svn 修饰词直接加载目录,zinit 会自动识别并加载补全。 2. 直接加载补全文件,此时需要使用 as="completion" 这个修饰词,它会让 zinit 将下一行命令加载的文件作为补全安装到指定目录:

zinit ice as="completion"
zinit snippet OMZ::plugins/cargo/_cargo

加载 OMZ 插件时需要注意的点

OMZ 的部分插件/主题会依赖 OMZ 本身提供的功能。比如 git 插件,如果想要正常使用的话,需要加载 OMZ 的 git 库。

# 加载 OMZ 的 git 库
zinit snippet OMZ::lib/git.zsh
# 加载 OMZ 的 git 插件
zinit snippet OMZ::plugins/git/git.plugin.zsh

也就是说,虽然 zinit 并没有提供直接加载 OMZ 框架的能力,但是你可以使用 snippet 功能选择性加载框架的部分功能。这比其他插件管理器的做法更为灵活。

比如我的配置里就直接加载了 OMZ 的部分库,如果你也是 OMZ 的用户,建议同样加载这些库,能保证体验一致。(比如键位绑定)

zinit snippet OMZ::lib/clipboard.zsh
zinit snippet OMZ::lib/completion.zsh
zinit snippet OMZ::lib/history.zsh
zinit snippet OMZ::lib/key-bindings.zsh
zinit snippet OMZ::lib/git.zsh
zinit snippet OMZ::lib/theme-and-appearance.zsh

Turbo Mode

终于说到 zinit 的最强 feature 了。没有 Turbo Mode 的 zinit 不过是略为优秀的插件管理器。然而有了 Turbo Mode,zinit 可以说是顶级 zsh 插件管理器,没有之一!

让人觉得作弊了的加速效果

这个功能非常强大,基本用法却很简单。举例来说,下面的代码,就实现了 OMZ git 插件的延迟加载。

zinit ice lucid wait='0'
zinit snippet OMZ::plugins/git/git.plugin.zsh

延迟加载用到了两个修饰词:lucid,用于静默加载。wait={秒},在 prompt 加载完毕后的若干秒后再加载。上面的例子就是让 prompt 启动后立刻加载 git 插件。

对于绝大多数插件来说,这两个参数已经足够了。但是有个别插件是例外,这样的例外很少,在这里就直接列出来了。

  1. zsh-autosuggestions
zinit ice lucid wait='0' atload='_zsh_autosuggest_start'
zinit light zsh-users/zsh-autosuggestions

zsh-autosuggestions 的延迟加载用到了 atload='_zsh_autosuggest_start' 修饰词,因为wait='0' 会让第一个 prompt 加载完成后再加载 zsh-autosuggestions,于是第一个 prompt 就无法使用 autosuggesstions,必须手动激活。

2. 补全类插件

比如 “zsh-users/zsh-completions”,延迟加载补全可能导致无法补全。修复手段当然是有的,但是延迟加载补全的意义并不大,因为本身并不耗时,建议不要延迟加载补全。

不过即使不延迟加载补全,可能也会发现补全用不了,而且有些自带补全的插件一旦延迟加载就会导致补全失效。

如果没有延迟加载与补全相关的插件,可以简单地在配置末尾添加 autoload compinit; compinit; zinit cdreplay -q 来手动初始化补全。

如果延迟加载了补全相关的插件就比较麻烦了,需要给最后一个补全相关的插件添加 atload="zpcompinit; zpcdreplay"修饰, 相当于同时延迟初始化补全。

# 假设 git 插件是最后加载的
zinit ice lucid wait="0" atload="zpcompinit; zpcdreplay"
zinit snippet OMZ::plugins/git/git.plugin.zsh

3. 主题

主题的延迟加载是可行并且有意义的,可以起到 powerlevel10k 主题中 “Instant Prompt” 的效果。即先提供一个精简的 prompt,主题加载完毕后再切换到完整版。

越是重量级的主题,加速效果越是明显,然而同样配置起来越是复杂。事实上如果将其他耗时插件都延迟加载后,启动时间应该已经降到了 100ms 以下,不必延迟加载主题也已经够快了。

如果坚持追求极致速度的话,可以参考:zplugin_tutorial/#turbo-mode-加载复杂的命令提示符

主题推荐

前面提到 zsh 速度慢主要有两个方面,一个是插件过多造成的启动速度慢,这个可以通过 zinit 解决。

然而另一个 prompt 反应过慢,就只能通过更换主题解决了。当然,并不是更换简陋的主题,而是更先进的异步主题!异步主题能够在后台进行耗时的 git 信息统计等操作,完成后再刷新 prompt,有效避免了进入大 repo 的卡顿。

Pure 主题
robbyrussell 主题

首先 OMZ 内置主题全部排除!这些主题都不是异步主题,进入大 repo 时卡顿非常明显。

这里只推荐两个

  1. pure

我目前使用的主题,不过定制能力不强,我不得不修改源码以符合自己的使用习惯

  1. powerlevel10k

powerlevel9k 的进化版本,速度和定制能力都很强的主题,但是瞅着有些复杂最终被我拒绝(嘛,也说不定哪天就真香了)

(更新:我已经真香了,强烈推荐这个主题,自带的 Instant Prompt 功能可以让你的 zsh 启动瞬间就进入可用状态)

参考配置

直接 Copy & Paste

  1. 作者的配置:https://github.com/zdharma/zinit-configs/blob/master/psprint/zshrc.zsh
    非常复杂,用到了很多 zinit 的高级功能,好处是兼容性强,不必依赖包管理来安装插件(比如 fzf, ripgrep 一类的)
  2. 我的配置:Aloxaf/dotfiles
    非常简单的配置,我认为第三方命令行工具应该交给包管理器来管理,zinit 只用来管理那些包管理器管理不了的文件

文章来源: https://zhuanlan.zhihu.com/p/98450570
如有侵权请联系:admin#unsafe.sh