自作のシェルプロンプト、almelを非同期対応させることで巨大なリポジトリ内での硬直を軽減し、使用感を向上させました。
Almel とは
almel はRust製の高速に動作するシェルプロンプトです。 現在はBash, Zsh, fishに対応しています*1。
以前の記事:
ryooooooga.hateblo.jp ryooooooga.hateblo.jp
Almelはlibgit2を用いることで高速にGitステータスを表示しています。
ですがこれまでは常に同期的にGitステータスを取得していたため、巨大なリポジトリ内では場合によって描画に〜1秒程度の時間がかかることがあり、発生する待ち時間がストレスになることがありました。
既存の非同期プロンプト
Powerlevel10kやagkozak Zsh Promptなどの既存のZshプロンプトの中には、非同期に描画することでスループットを向上させているものがあります。
これらは、Gitステータスなどの実行に時間の掛かるセグメントを除外した"仮のプロンプト"を同期的に表示し、その後Gitステータスを含む完全なプロンプトを非同期的に描画することでコマンド実行後の硬直を軽減し、利用体験を向上させています。
Almelの非同期対応
今回、Almelを非同期対応させることでこれらと同様に利用体験を向上させました*2。
非同期描画にはzsh-asyncを利用しているため、あらかじめなんらかの方法でzsh-asyncをインストールしておく必要があります*3。
初期化方法:
# .zshrc # zsh-asyncを読み込む source "${zsh-asyncのインストールディレクトリ}/async.zsh" # Almelを初期化する eval "$(almel init zsh --async)"
Zinitを用いた初期化方法:
# .zshrc zinit light-mode from'gh-r' as'program' for \ atload'eval "$(almel init zsh --async)"' @'Ryooooooga/almel' zinit wait lucid blockf light-mode for \ @'mafredri/zsh-async'
パフォーマンスについて
では、もともとGitに関する情報の取得にどの程度の時間がかかっていたのか測定してみます。
小さいリポジトリでの計測
まず比較的小規模であるAlmel自身のリポジトリを用いて、Gitリポジトリに関する情報取得の有無による実行速度の差を確認します。
# Gitステータスあり $ time (for i in {1..100}; do almel prompt -s0 -j0 -d1 zsh >/dev/null; done) ( for i in {1..100}; do; almel prompt -s0 -j0 -d1 zsh > /dev/null; done; ) 1.16s user 0.71s system 83% cpu 2.248 total # 平均 22.48ms (N=100) # Gitステータスなし $ time (for i in {1..100}; do almel prompt -s0 -j0 -d1 zsh --no-git >/dev/null; done) ( for i in {1..100}; do; almel prompt -s0 -j0 -d1 zsh --no-git > /dev/null; ; 0.83s user 0.46s system 78% cpu 1.629 total # 平均 16.29ms (N=100)
ここで利用している almel prompt
コマンドはAlmelのプロンプトの実際の表示を行っているコマンドで、--no-git
オプションを渡すことでGit関連の機能を全て無効化できます。
差はわずかではありますが、当然ながら--no-git
オプションありのほうが速いことがわかります。
Profiling Rust - macOS で DTrace を使って FlameGraph を描画する - Qiita を参考にプロファイリングを行ってみると、Gitステータスの取得 (下図 右 緑矩形) に最も時間が掛かり、次いで Gitリポジトリの初期化 (下図 左 青矩形) に時間が掛かっていることがわかります。
Gitステータスの原理からして、リポジトリに含まれるファイルの数が多いほどより多くの時間が必要になります。
やや大きいリポジトリでの計測
続いて、手元にあったリポジトリの中で最も体感の描画時間が長かった lazygit を用いて計測を行います。
# Gitステータスあり $ time (for i in {1..100}; do almel prompt -s0 -j0 -d1 zsh >/dev/null; done) ( for i in {1..100}; do; almel prompt -s0 -j0 -d1 zsh > /dev/null; done; ) 10.24s user 15.13s system 95% cpu 26.438 total # 平均 264.38ms (N=100) # Gitステータスなし $ time (for i in {1..100}; do almel prompt -s0 -j0 -d1 zsh --no-git >/dev/null; done) ( for i in {1..100}; do; almel prompt -s0 -j0 -d1 zsh --no-git > /dev/null; ; 0.47s user 0.42s system 73% cpu 1.207 total # 平均 12.07ms (N=100)
Gitステータスありの場合、260ms以上も掛かっていることがわかります。 これほどになると同期描画の硬直はストレスに感じます。
まとめ
Almelのプロンプトの描画を非同期化することで利用体験を向上させました。
以前からGitステータス取得のパフォーマンスの低下は懸念点であったため、それが解消できたことは喜ばしいです。
また、almel init zsh --async
で出力されるスクリプト(下記)は非常に短く簡単であるため非同期プロンプトを実装する際の参考になるでしょう。