Fish Shellの設定ファイルを見直して起動時間を 470ms -> 14.7ms に短縮した話
はじめに
数日前にこのような記事を見かけました。
この記事では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:.
最適化の流れ
それでは、いかにして起動時間を短縮したかを見ていきます。
最適化前
設定
まずは現状の起動時間を計測してみます。
❯ 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_script
をbassというfish pluginを使ってconfig.fish
から読み込む
という運用をしていました。
.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_script
とconfig.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 として紹介されることも多く、自分自身長らく使っていましたが、こちらも起動時間に影響があることが判明しました。
そこで、この機能自体を自前実装することにしました。
➜ 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で動かすことにしました。
➜ 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 を実装することにしました。
念の為コードの流れを解説すると、
-
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に移行する計画も進んでいるので、今後さらに高速化するかもしれないと考えるとワクワクしますね!
最終的な設定
Discussion
その後テーマをこちらにしました