ぶていのログでぶログ

思い出したが吉日

starship+asdfでプロンプトの表示が遅くなるのを改善する

タイトルの通り。 promptのカスタマイズにはstarshipを使っていて*1、バージョン管理にはasdfを使うようにここ最近変更した。 それぞれ特に不満なく使っていたのだが、あるときからリポジトリ内でのプロンプトの表示が遅いというのに気がついてしまった。 starshipがWARNINGを吐いていたこともあり、被疑はstarshipにあるとわかっていた。 ↓WARNING例

[WARN] - (starship::utils): Executing command "ruby" timed out.
[WARN] - (starship::utils): You can set command_timeout in your config to a higher value to allow longer-running commands to keep executing.

starshipにはexplainというサブコマンドがありこれで各モジュールの実行速度がわかるのでこれを実行した。

❯ starship explain

 Here's a breakdown of your prompt:
 hogehoge  (1ms)                -  The current working directory
 on  branch  (<1ms)         -  The active branch of the repo in your current directory
 via 💎 v3.0.2  (128ms)       -  The currently installed version of Ruby
 via ⍱  (<1ms)                -  The currently installed version of Vagrant
 ❯  (<1ms)                    -  A character (usually an arrow) beside where the text is entered in your terminal

この結果を見るとrubyの実行に時間がかかっているのがわかる。 バージョンを出すだけなのになぜこんな時間がかかるのか調べたところ、asdfのラッパーに原因があることがわかった。 というのもrubyコマンドを実行すると呼び出されるのが ~/.asdf/shims/ruby なのだがこれはシェルスクリプトになっていて、内部的には ~/.asdf/bin/asdf を呼び出している。 この ~/.asdf/bin/asdf もシェルスクリプトになっているので、rubyを呼び出すまでにbashが2回呼び出されることになる。 これが遅くなっている原因ということがわかった。

解決策: asdf-direnvを使う

解決策がないか色々検索していたらasdfのリポジトリに同様の問題と思われるissueが起票されていた。 このissueはクローズされいないものの、issueコメント中でasdf-direnvが紹介されており試したところ解決した。

github.com

READMEがしっかりしているので見ればわかるのだが、一応インストール手順を書いておくと

1 asdf-direnvをインストールする。

asdf plugin-add direnv
asdf install direnv latest
asdf global direnv latest

2 シェル起動時フックを設定する。READMEではbashになっているが、私はfish-shellなのでそれに合わせて変更。

asdf exec direnv hook fish | source

3 direnvrcに設定をおこない use asdfを利用できるようにする*2

❯ cat ~/.config/direnv/direnvrc
source "$(asdf direnv hook asdf)"

4 プロンプトが遅くなるディレクトリに移動し .envrcuse asdf を設定する。

❯ cd /path/to/hoge
❯ cat .envrc
use asdf

5 direnv を有効化する。有効化するとenvファイルのキャッシュが作られて、そのキャッシュを読み込むようだ。

❯ direnv allow
direnv: loading /path/to/hoge/.envrc
direnv: using asdf
direnv: Creating env file ~/.asdf/installs/direnv/2.28.0/env/3677896208-2306459982-2809058868-537363346
direnv: loading ~/.asdf/installs/direnv/2.28.0/env/3677896208-2306459982-2809058868-537363346
direnv: using asdf direnv 2.28.0
direnv: using asdf perl 5.34.0
direnv: using asdf delta 0.7.1
direnv: using asdf ghq 1.1.7
direnv: using asdf starship 0.56.0
direnv: using asdf terraform 1.0.2
direnv: using asdf ruby 3.0.2
direnv: using asdf python 3.9.5
direnv: export +RUBYLIB ~PATH

作成されるenvファイルのキャッシュをみるとわかるが、asdfがインストールした各ツールの実体 (~/.asdf/installs/<tool-name>//bin/<tool-name>)を直接 $PATH に設定しているようだった。 前述のbashスクリプトを呼び出さない分速くなるという寸法のようだ。

効果とデメリット

では実際どのくらい速くなるのか? READMEにベンチマークの項目がありそこでは node --version が 190ms -> 4ms になっており98%改善していると書いてある。 私の環境ではどうなったか?設定後の starship explain をとってみた。

❯ starship explain

 Here's a breakdown of your prompt:
 hogehoge  (1ms)                -  The current working directory
 on  branch  (2ms)          -  The active branch of the repo in your current directory
 via 💎 v3.0.2  (18ms)       -  The currently installed version of Ruby
 via ⍱  (<1ms)               -  The currently installed version of Vagrant
 ❯  (<1ms)                   -  A character (usually an arrow) beside where the text is entered in your terminal

設定前の結果と比べて 128ms -> 18msになっている! 試行回数が少ないのでばらつきはあるだろうが気にならないレベルでプロンプトの表示が速くなったので満足。

しかしながら、デメリットがありこの高速化の恩恵を受けるには各ディレクトリで .envrcの設定と direnv allowの実行が必要である。 まぁ、シンプルなコマンドなのでワンライナーを作ってもいいかもしれない。

まとめ

asdf-direnv を使うことでプロンプトの表示が遅くなる問題を解決することができてよかった。 少し手間が増えたものの恩恵は大きいのでこれからも使っていこうと思う。 今回デバッグするにあたり startship explain というコマンドを知ったのだが、これは最近できたコマンドなのかな? とても便利だったので今後何かあったら使っていきたい。

*1:いつのまにか日本語サイトもできていてすごい

*2:ここはどうも内部的にbashで実行されているみたいだ