@ryoppippi

Fish Shellの設定ファイルを見直して起動時間を 470ms -> 14.7ms に短縮した話

17 Sept 2023 ・ 11 min read


はじめに

数日前に​このような​記事を​見かけました。

https://zenn.dev/fuzmare/articles/zsh-plugin-manager-cache

この​記事では​Zshの​起動時間を​大幅に​短縮する​方​法が​紹介されています。

さて、​自分は​ここ数年(と​いうか​コードを​書き始めてから​ずっと​)、Fish Shellを​使っています。 Fish Scriptが​とても​書きやすく、​補完が​とても​優秀なので​ずっと​気に入って​使ってます。 しかし、​起動時間が​遅いなと​なんとなく​感じていたので、​この機会に​設定を​見直してみました。

自分自身のマシン環境
➜ neofetch
                    'c.          ryoppippi
                 ,xNMM.          ------------------------------------
               .OMMMMo           OS: macOS 13.5.1 22G90 arm64
               OMMM0,            Host: Macmini9,1
     .;loddo:' loolloddol;.      Kernel: 22.6.0
   cKMMMMMMMMMMNWMMMMMMMMMM0:    Uptime: 3 days, 2 hours, 53 mins
 .KMMMMMMMMMMMMMMMMMMMMMMMWd.    Packages: 1 (brew)
 XMMMMMMMMMMMMMMMMMMMMMMMX.      Shell: fish 3.6.1
;MMMMMMMMMMMMMMMMMMMMMMMM:       Resolution: 2560x1080, 1080x1920
:MMMMMMMMMMMMMMMMMMMMMMMM:       DE: Aqua
.MMMMMMMMMMMMMMMMMMMMMMMMX.      WM: Rectangle
 kMMMMMMMMMMMMMMMMMMMMMMMMWd.    Terminal: WezTerm
 .XMMMMMMMMMMMMMMMMMMMMMMMMMMk   CPU: Apple M1
  .XMMMMMMMMMMMMMMMMMMMMMMMMK.   GPU: Apple M1
    kMMMMMMMMMMMMMMMMMMMMMMd     Memory: 3159MiB / 16384MiB
     ;KMMMMMMMWXXWMMMMMMMk.
       .cooc,.    .,coo:.

最適化の​流れ

それでは、​いかに​して​起動時間を​短縮したかを​見ていきます。

最適化前

設定

https://github.com/ryoppippi/dotfiles/blob/7289a3bfe61b4ab53fac3348ac25f957369f208b/fish/config.fish https://github.com/ryoppippi/dotfiles/blob/7289a3bfe61b4ab53fac3348ac25f957369f208b/fish/fish_plugins

まずは​現状の​起動時間を​計測してみます。

❯ hyperfine -w 5 -r 50 'fish -i -c exit'
Benchmark 1: fish -i -c exit
  Time (mean ± σ):     465.4 ms ± 128.8 ms    [User: 177.8 ms, System: 83.8 ms]
  Range (min … max):   411.2 ms … 1203.2 ms    50 runs

…​ ​おっそ。 いく​つか​心当たりは​あります。​なので、​それを​順番に​見ていきます。

.bash_profileの​読み込みを​やめる

これまでの​自分の​設定では、

  • 環境変数/Path等の​設定を.bash_script に​記述
  • .bash_scriptbassと​いう​fish pluginを​使って​ config.fish から​読み込む

と​いう​運用を​していました。

https://github.com/ryoppippi/dotfiles/blob/7289a3bfe61b4ab53fac3348ac25f957369f208b/fish/config.fish#L1-L3

.bash_profile

https://github.com/ryoppippi/dotfiles/blob/7289a3bfe61b4ab53fac3348ac25f957369f208b/bash/.bash_profile

な​ぜこうしていたかと​いえば、​Fish Scriptは​posix準拠ではないからです。 後々bash/zsh等の​posix準拠な​シェルに​移行しやすいように、​環境変数や​Path等の​シェル間で​使いまわせそうな​設定は.bash_profileに​記述していました。

とは​いえ、​昨今の​状況を​踏まえると、​わざわざfishと​bash/zsh scriptを​併用する​意味は​薄いと​考えました。​理由と​しては、

  • 現状fishから​移行する​予定は​当分ない
  • Fish Scriptの​方が​書きやすい
  • もし移行する​必要に​迫られた​としても、​ChatGPT等の​LLMに​変換して​もらえば​いい

と​いう​ことで、.bash_profileの​読み込みを​やめ、​全ての​設定をconfig.fishに​移行しました。

❯ hyperfine -w 5 -r 50 'fish -i -c exit'
Benchmark 1: fish -i -c exit
  Time (mean ± σ):     329.9 ms ±  16.5 ms    [User: 121.4 ms, System: 70.4 ms]
  Range (min … max):   313.7 ms … 419.9 ms    50 runs

これだけで​150ms 程度短縮できました。 bashの​プロセスを​fishから​起動するのに​かかっていた​時間や、.bash_scriptconfig.fishで​重複していた​処理を​削減できたことが​大きかったようです。

Starshipを​やめる

Starshipは、​fish/zsh/bashの​見た​目を​カスタマイズする​ための​ツールです。 Starshipは​設定を​ほぼ書かずに​綺麗な​プロンプトを​作る​ことができるので、​とても​人気が​あります。 自分も​長らく​これを​使っていました。 しかし、​試しに​これを​抜いてみると、​起動時間が​大幅に​短縮されました。

➜ hyperfine -w 5 -r 50 'fish -i -c exit'
Benchmark 1: fish -i -c exit
  Time (mean ± σ):     228.6 ms ±   5.1 ms    [User: 95.8 ms, System: 57.4 ms]
  Range (min … max):   222.2 ms … 249.4 ms    50 runs

なんと​100ms以上​短縮されました。 正直、​Starship以外にも​pure Fish Scriptで​書かれた​プラグインが​いくつも​あるので、​それに​乗り換える​ことにしました。 自分は​いく​つかの​プラグインを​試した後、spacefishに​落ち着きました​(皮肉な​ことに、​この​spacefishは​Public Archiveされており、​開発者は​Starshipへの​移行を​推奨していますが、​すんなり動いたので​そのまま​使っています)。

franciscolourenco/done を​やめる

franciscolourenco/doneは​一定​時間以上​かかる​コマンドが​終了した​ときに​通知を​出してくれる​fish pluginです。 おすすめの​fish plugin と​して​紹介される​ことも​多く、​自分​自身長らく​使っていましたが、​こちらも​起動時間に​影響が​ある​ことが​判明しました。 そこで、​この機能自体を​自前実装する​ことにしました。

https://github.com/ryoppippi/dotfiles/blob/b40e4e6ddd6ee7c5be8786d280df4be6f7c2be00/fish/user_functions/fish_right_prompt.fish

➜ hyperfine -w 5 -r 50 'fish -i -c exit'
Benchmark 1: fish -i -c exit
  Time (mean ± σ):     217.9 ms ±  18.8 ms    [User: 87.4 ms, System: 50.5 ms]
  Range (min … max):   204.9 ms … 319.5 ms    50 runs

これで​さらに​10ms 程度短縮できました。

処理の​一部を​Backgroundで​動かす

Fish Scriptは、​関数の​最後に​ & を​つける​ことで​Background で​処理を​動かすことができます。 これを​使って、​起動時に​必要な​処理の​一部を​Backgroundで​動かすことにしました。 https://github.com/ryoppippi/dotfiles/commit/d27d1bc64610d70d5436acda25e67a9445d3d755

➜ hyperfine -w 5 -r 50 'fish -i -c exit'
Benchmark 1: fish -i -c exit
  Time (mean ± σ):     206.7 ms ±   7.4 ms    [User: 85.0 ms, System: 47.9 ms]
  Range (min … max):   198.5 ms … 233.7 ms    50 runs

またまた​10ms 程度短縮できました。

メインディッシュ: cacheを​実装する

ここまでで、​起動時間は​200ms程度まで​短縮されました。 しかし、​まだまだ​遅いです。

原因を​探ってみると、​外部​コマンドを​叩いている​部分が​ボトルネックに​なっている​ことが​わかりました。 自分のconfig.fish では、​以下の​コマンドが​呼ばれていました。

  • xcode-select
  • brew
  • gem
  • direnv
  • zoxide
  • starship(一応上の​項で​削除済み)

そこで、​先の​Zshの​記事を​参考に、​cache を​実装する​ことにしました。

https://github.com/ryoppippi/dotfiles/blob/96a3cdcf8442bffa2525229e7a5fe70515bae1d7/fish/config.fish#L99-L120

念の​為コードの​流れを​解説すると、

  • config.fishの​更新日時が~/.cache/fish/config.fish の​更新日時より​新しい​場合、​または~/.cache/fish/config.fishが​存在しない​場合、​cache を​更新する
  • 外部​コマンドの​実行結果を~/.cache/fish/config.fish に​保存する
  • ~/.cache/fish/config.fish を​読み込む

と​する​ことで、​cache が​存在する​場合は​外部​コマンドを​実行せずに​済むようにしました。

この​結果、

➜ hyperfine -w 5 -r 50 'fish -i -c exit'
Benchmark 1: fish -i -c exit
  Time (mean ± σ):      14.7 ms ±   0.6 ms    [User: 9.4 ms, System: 4.3 ms]
  Range (min … max):    13.9 ms …  16.2 ms    50 runs

なんと​起動時間が​ 14.7ms にまで​短縮されました! 一気に​190ms 程度短縮できました。 やったね!

ちなみに

念の​為、​cache の​結果を​ファイルではなく​ set -U CACHE などと​環境変数に​保存する​方​法も​試してみました。 しかし​予想に​反して、​手元の​環境では​ファイルに​保存する​場合と​速度に​大差が​ありませんでした。 また、​更新の​タイミングを​決定する​コードが​煩雑に​なりそうでした​(環境変数に​保存する​場合は​現在の​時刻を​別個保存する​必要が​あるが、​ファイルに​保存する​場合はtestコマンドを​用いて​ファイルの​更新日時を​比較すれば​いいだけなので​実装が​容易)。 その​ため、~/.cache 以下に​cache ファイルを​保存する​ことにしました。

まとめ

以上のように、​設定を​見直す​ことで、​Fish Shellの​起動時間を​470ms -> 14.7 ms にまで​短縮する​ことができました。 Zshの​高速化で​必須と​される​遅延読み込みやzcompileに​よる​最適化と​いった​テクニックを​一切​使わずに、​これだけの​高速化が​できたのは​驚きでした。 Fish Scriptの​実行速度が​十分に​速い​おかげかもしれません。 Fish自体の​実装を​C++から​Rustに​移行する計画も​進んでいるので、​今後さらに​高速化するかもしれないと​考えると​ワクワクしますね!

最終的な設定

https://github.com/ryoppippi/dotfiles/blob/6f56dc22da020c9e4f24c2e7204a1535d2b7f746/fish/config.fish https://github.com/ryoppippi/dotfiles/blob/7289a3bfe61b4ab53fac3348ac25f957369f208b/fish/fish_plugins

comment on bluesky / twitter
CC BY-NC-SA 4.0 2022-PRESENT © ryoppippi