肉球でキーボード

MLエンジニアの技術ブログです

pyenv-virtualenv initでプロンプト表示速度が低下する問題

問題

~/.zshrcに記述した eval "$(pyenv virtualenv-init -)" によって、zshのプロンプト表示が遅くなってることに気づきました。

原因と解決方法を調べたのでまとめました。

結論

~/.zshrcに記述するのを

eval "$(pyenv virtualenv-init -)"

eval "$(pyenv virtualenv-init - | sed s/precmd/chpwd/g)"

に変更する

pyenv-virtualenv initとは?

GitHub - pyenv/pyenv-virtualenv: a pyenv plugin to manage virtualenv (a.k.a. python-virtualenv).

カレントディレクトリにある.python-versionの設定に基づいて、python仮想環境を自動的にactivate/deactivateします。

.python-version ファイルは、pyenv でローカルのPython バージョンを設定するために、pyenv local コマンドで作成・削除できます。

原因

展開されるシェルスクリプトの内容を確認します。

$ echo $(pyenv virtualenv-init -)

以下のシェルスクリプトが実行されています。処理内容を見ていきます。

export PATH="/Users/satsuki/.pyenv/plugins/pyenv-virtualenv/shims:${PATH}"
export PYENV_VIRTUALENV_INIT=1

_pyenv_virtualenv_hook() {
    local ret=$?
    if [ -n "${VIRTUAL_ENV-}" ]; then
        eval "$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
    else
        eval "$(pyenv sh-activate --quiet || true)" || true
    fi
    return $ret
}

typeset -g -a precmd_functions

if [[ -z $precmd_functions[(r)_pyenv_virtualenv_hook] ]]; then
    precmd_functions=(_pyenv_virtualenv_hook $precmd_functions)
fi

_pyenv_virtualenv_hook という関数が定義されています。この関数では以下の処理が実行されます。

  • 仮想環境が存在する場合、環境をactivateし、失敗した場合deactiveを行う
  • 仮想環境が存在しない場合、環境をactivateする

zshでは precmd_functions というグローバルな配列で、プロンプトが変更された時に実行する関数を管理しています。

zsh: 9 Functions

作成した_pyenv_virtualenv_hook 関数を precmd_functions 配列に追加しています。

これにより、プロンプトが変更されるたびにvirtualenvの仮想環境のactivateが実行され、zshの動作が重くなります。

解決方法

pyenv-virtualenvのGitHub Issueでシェル動作が遅くなる現象のDiscussionがあり、回避方法が提案されています。

Slow shell performance after running pyenv virtualenv-init · Issue #259 · pyenv/pyenv-virtualenv · GitHub

eval "$(pyenv virtualenv-init)” の代わりに以下を ~/.zshrcに書き込みます

eval "$(pyenv virtualenv-init - | sed s/precmd/chpwd/g)"

上記のコマンドでは、先ほどのpyenv virtualenv-init で展開されるシェルスクリプト内のprecmdの文字列をchpwdに書き換えています。

chpwd_functions というグローバルな配列で、カレントディレクトリが変更された時に実行する関数を管理しています。

よって、プロンプトを変更する度に仮想環境をactivateしていた処理を、カレントディレクトリを変更する度に実行するよう変更しています。

プロンプト表示速度の変化

zsh-prompt-benchmarkzshのプロンプトレンダリング速度を測ってみました。

修正前

********************************************************************
                      Prompt Benchmark Results
********************************************************************
Warmup duration      8s
Benchmark duration   2.050s
Benchmarked prompts  7
Time per prompt      292.84ms  <-- prompt latency (lower is better)
********************************************************************

修正前

修正後

********************************************************************
                      Prompt Benchmark Results
********************************************************************
Warmup duration      8s
Benchmark duration   2.015s
Benchmarked prompts  78
Time per prompt      25.84ms  <-- prompt latency (lower is better)
********************************************************************

修正後

プロンプト表示速度が大きく改善しました。

参考