古いMacを使っている際に、ターミナルが開くまでの時間がやけにかかっていることに気が付きました。

ハードウェアの処理速度が遅いのでしかたないのかなと思っていたのですが、詳しく調べみると.zshrcの中のrbenv initでほとんどの時間を費やしていることがわかりました。

本記事ではzshの起動時間を計測・分析し、rbenv initが原因のケースの対策をまとめます。


起動時間の計測

まず全体の起動時間を把握するのが重要です。以下のコマンドで測ることができます。

time zsh -i -c exit
zsh -i -c exit  2.30s user 0.86s system 98% cpu 3.197 total

3秒を超えているなら明らかに遅いです(手元の環境では4秒〜5秒もかかっていました)。1秒以下が快適な目安になります。


ボトルネックの特定

全体の時間がわかったら、次はどこが遅いかを特定します。zprofを使うのが定番です。

.zshrc先頭に追加します:

zmodload zsh/zprof

.zshrc末尾に追加します:

zprof

新しいターミナルを開くと各関数の実行時間が表示されます。確認後は両行を削除してください。

より詳細にトレースしたい場合は行単位での計測も可能です。

# .zshrcの先頭に追加
setopt XTRACE
PS4=$'%D{%M:%S.%.} '
exec 2>/tmp/zsh_trace.log

# .zshrcの末尾に追加
unsetopt XTRACE
cat /tmp/zsh_trace.log

rbenv initが原因の場合

zshの起動が遅いケースでよく名前が挙がるのがrbenv initです。eval "$(rbenv init -)"は毎回シェル関数を動的に生成するため重くなります。

対策1: –no-rehash を指定する

# 変更前
eval "$(rbenv init -)"

# 変更後
eval "$(rbenv init --no-rehash - zsh)"

--no-rehashは起動時のrbenv rehash(shimの再生成)をスキップするオプションです。rbenvの公式ドキュメントにも明記されており、rbenv initコマンド自体がzsh向けの設定を生成する際に--no-rehash付きで出力するようになっています。

トレードオフ: gem installで新しいコマンドを持つgemを入れた後、shimが自動更新されなくなります。その場合は手動でrbenv rehashを実行する必要があります。

gem install rails
rbenv rehash  # これが必要になる
rails new myapp

改善幅は環境によりますが、環境によっては数秒起動が早くなるはずです。実際手元の環境ではこの方法で十分なところまでいきました。


対策2: 評価結果をキャッシュする

rbenv initの出力をファイルにキャッシュし、2回目以降はそれをsourceするだけにします。I/Oの回数を減らせるため、効果が大きいです。

_rbenv_cache="$HOME/.rbenv_init_cache.zsh"

if [[ ! -f "$_rbenv_cache" \
  || "$(which rbenv)" -nt "$_rbenv_cache" \
  || "$HOME/.rbenv/version" -nt "$_rbenv_cache" ]]; then
  rbenv init --no-rehash - zsh > "$_rbenv_cache"
fi

source "$_rbenv_cache"

--no-rehashと組み合わせることで効果が高まります。

注意点:

  • rbenvをbrew upgrade等で更新した場合にキャッシュが古いままになることがあります
  • その場合は手動でキャッシュを削除してください
rm ~/.rbenv_init_cache.zsh

対策3: 遅延ロード

rbenvを最初に使用するまで読み込みを遅らせます。設定がシンプルで、デメリットも少ないです。

rbenv() {
  unfunction rbenv
  eval "$(command rbenv init - zsh)"
  rbenv "$@"
}

ターミナルを開いただけでは初期化せず、rbenvrubyを初めて叩いたタイミングで初期化が走ります。普段ターミナルを開いてすぐrubyを使わないなら、これが最もシンプルな解決策です。


対策の比較

対策 効果 デメリット 手軽さ
--no-rehash 小〜中 gem install後に手動rehashが必要
キャッシュ キャッシュが古くなることがある
遅延ロード 最初の実行時だけ若干遅い

改善後の確認

time zsh -i -c exit

1秒を切れば十分快適です。さらに追い込みたい場合はcompinitの最適化やzsh-deferによる非同期ロードも検討する価値があります。