zsh 加速

 

zsh 加速这里主要是说一下最近针对开启速度的优化,具体执行速度我估计也不好整,众所周知,zsh 要是加上一些插件的话,再加上开发用到的一大堆环境的声明,最后的开启时间真的感人,常用的几种shell对比起来的话,fish,zsh,bash,这三个里面,zsh 可以说是最慢的,fish 很美好,但是其有时候语法和bash 不兼容,之后bash又有些难用,还是得回到zsh的怀抱,但是面对现在我这...

zsh 加速

这里主要是说一下最近针对开启速度的优化,具体执行速度我估计也不好整,众所周知,zsh 要是加上一些插件的话,再加上开发用到的一大堆环境的声明,最后的开启时间真的感人,常用的几种shell对比起来的话,fish,zsh,bash,这三个里面,zsh 可以说是最慢的,fish 很美好,但是其有时候语法和bash 不兼容,之后bash又有些难用,还是得回到zsh的怀抱,但是面对现在我这里几乎一两秒的开启速度,可见的打开延迟,让我非常糟心,之后思考,怎么优化这个速度。当然有人觉得1s 2s还慢?这就需要做一个简单的测试,看一看 bash 和 fish 的水平:

for i in $(seq 1 5); do /usr/bin/time fish -i -c exit; done

把中间的 fish 改一下就可以大致测试其他shell的速度,大致上是这样:

Screen Shot 2020-07-03 at 10.36.21

需要对 zsh 的开启速度进行优化,要不然这个开启速度对比太明显了。

zinit

大部分人会直接 oh-my-zsh 来直接搞,OMZ 太有名了,但是这样感觉不好管理插件,而且加载插件速度是比较慢的,需要一个管理器,一开始我使用的是 zplug,当时看到说是可以很快速的加载插件,大致原理好像是并行的启动的时候加载多个插件,不过后来在接触zinit之后发现,发现zplug好像实现的不大行,还是很慢,感觉有时候甚至比 OMZ 慢,zinit,虽然感觉使用的人少,但是发现它的实现算是非常好,使用另一种思路,打开shell的时候,肯定不会立刻用到所有插件,可以先不加载,只先加载基础功能,不要影响启动。zinit 的项目地址在这里,安装按照 readme 来就可以,下面是简单的介绍怎么使用,其实我也是用到了很小的一部分来加速启动。

首先来看在zinit中普通模式加载一个插件:

zinit light romkatv/zsh-defer

这样就加载了这个插件,如果需要加载 OMZ 插件,基本上是下面的样子:

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

这就是基本的加载模式,如果每个插件都用这种加载方式,那么不会开启速度的提升,下面介绍 zinit 最有用的工作模式,也就是 turbo 模式,最基础的使用方式是下面这样:

zinit ice wait'!0' 
zinit load halfo/lambda-mod-zsh-theme

这个的意思是在启动之后,也就是走完 zshrc 之后,0s 后立即(也就是立刻)开始加载这个插件,这样就可以将打开就需要的必要插件普通方式加载,而其他不太急需的插件都延后加载,这样就可以很大程度上降低开启时候的延迟,本质上没有说让加载过程变快,只是换用一种方式。但是这样写挺累的,zinit 给了一种 for 加载模式,长下面这个样子:

  • 普通加载

      zinit light-mode for \
          zinit-zsh/z-a-rust \
          zinit-zsh/z-a-as-monitor \
          zinit-zsh/z-a-patch-dl \
          zinit-zsh/z-a-bin-gem-node
    

    这样就不用每次都重复写zinit light 那么累了。

  • Turbo 模式

      zinit wait lucid for \
          zdharma/fast-syntax-highlighting \
          mafredri/zsh-async \
          OMZ::plugins/extract 
    

    这里的 lucid 我认为是指加载完后不提示信息,这里就是通过turbo模式加载多个插件的办法,把不是立刻就要使用的插件都用这种方式加载,而不是开启的时候就要全部加载完。

这大致上就是我使用到的部分,我没有特别深入的研究,这样操作之后,已经能很大程度上降低加载插件导致的速度问题。

最后说一下加载我喜欢的主题 pure 的方法:

zinit ice compile'(pure|async).zsh' pick'async.zsh' src'pure.zsh'
zinit light sindresorhus/pure

还是不够快

之后发现,怎么还不够快,其实这些就是环境变量等等东西背锅了,其中要点名批评 nvm,整个zsh 加载如果需要1s,nvm 加载能占 0.5s 多,几乎是破坏性的加载速度,但是我们还是需要用这种东西,就需要一种东西来让这些东西也延后加载一下,我没有仔细研究,zinit 怎么使用能够实现,我使用到了另一个插件,也就是上面说过加载方式的 zsh-defer 来实现我想要的功能,首先下用普通模式加载 zsh-defer,之后用conda举个例子:

conda_init_fun () {
    # Add by conda
    # >>> conda initialize >>>
    # !! Contents within this block are managed by 'conda init' !!
    __conda_setup="$('/Users/user/miniconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
    if [ $? -eq 0 ]; then
        eval "$__conda_setup"
    else
        if [ -f "/Users/user/miniconda3/etc/profile.d/conda.sh" ]; then
            . "/Users/user/miniconda3/etc/profile.d/conda.sh"
        else
            export PATH="/Users/user/miniconda3/bin:$PATH"
        fi
    fi
    unset __conda_setup
    # <<< conda initialize <<<
}

zsh-defer conda_init_fun

这里将所有的加载conda的语句放到一个函数中,之后让 zsh-defer 去加载,这样就不会导致卡在开启阶段没有一点反应了,虽然conda这样加载有后作用,因为conda需要在shell上修改,比如标示当前虚拟环境,这样加载之后会导致过一阵子shell才能输入文本,但是启动速度上就没有任何问题了。

类似这样的方法,可以将任何你想延后加载的脚本使用这种方式延后加载,让开启阶段变快,至少先进入到shell中,如果需要针对一句语句,可以这样操作:

zsh-defer -c '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"'

这样,暂时用不到的nvm就不至于在开启的时候卡住了,在特别极端的操作后,也就是几乎所有的包括环境变量,alias等,只要是打开shell阶段不会立即用到的,全部使用 turbo 模式或者是使用 zsh-defer 加载,开启速度达到了一个非常好的水平:

Screen Shot 2020-07-03 at 16.03.48

已经能够和 fish 一战,非常快的开启体验了,不过反作用就是开启的时候,我这里这样子激进的操作,开启的时候有很小的一段时间其实还是在加载东西,比如conda要加载出前面的 base 标示当前虚拟环境等,不过总比等白屏一点消息没有好。