それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

$(pyenv init -) が遅いので遅延ロードにした

ターミナルの起動が遅い。

最近、妙にターミナルの起動が遅くて苦痛だなと思って調べたら pyenv 関連だった。

rbenv もそうだったし、pyenv もやはり遅い。 rbenv に関してはrbenv init では諦めて、symlink を使って解決しているが pyenv は別の方法を模索した。

pyenv 関連の起動遅いねん

f:id:takuya_1st:20190414143941p:plain f:id:takuya_1st:20190414144043p:plain f:id:takuya_1st:20190414143856p:plain

ああ、わりと時間かかりすぎ。

## init 系を測定してみる
time { 
  ## rb
  eval "$(rbenv init -)"
  ## py
  eval "$(pyenv init -)"
  eval "$(pip completion --bash)"
  eval "$(pipenv --completion)"
}

シェル起動の処理で4秒超えは、ストレス溜まりそう。

f:id:takuya_1st:20190414151002p:plain

ターミナルの起動を早くしたい。

python は使わないこともあるし、ターミナル自体はもっと早く起動してサクサクと使いたい。

lazyload をする方法が紹介されている

ログインシェルの起動を高速化する lazyenv を作った - Qiita

方針としては

  1. pyenv と同名の function pyenv(){} をbashrcに登録する
  2. pyenv 関数内で、自分自身を消す
  3. 消すときに同時にpyenv をちゃんとロードする
  4. 自分自身のかわりにロードしたpyenv を起動する。

これらの関数をpyenv の初期化に変わって実行すると、bashrc 内部で init が行われないので、bashrcの読み込みが、遅くなることはない。

シェルスクリプトで表すとこんな感じ。

pyenv () {
    unset -f pyenv
    pyenv_load_function
    pyenv "$@"
}

上記の関数は、関数名とロード関数名が同じであれば、抽象化できるので、作ろうと思ったら、すでに作ってる人がいた。

使い方

私は、遅延ロードさせる関数を .bashrc.d/ に配置した。

.bashrc

bashrc.d をロードする。

if [[ -d ~/.bashrc.d/ ]]; then
  for i in ~/.bashrc.d/* ; do source $i; done
fi

以下のファイル名にして、layzyenv を先に読み込まれるように設定。

  • .bashrc.d/00-lazyenv.bash
  • .bashrc.d/pyenv-init.bash

.bashrc.d/00-lazyenv.bash

これは、takezoh/lazyenv から取得した。

.bashrc.d/pyenv-init.bash

## 2019-04-13 takuya
## python 関連の設定をまとめる
## lazyload を使って pip を遅延させる
# これをすることでコマンドを実行するまで補完ができなくなるので
# 初回起動まで、補完が出来なかったりする
# なので、一見すると補完が効かないように見えることがあるので注意
## pyenv init -
## pip completion
## pipenv --completion

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
export PYTHONSTARTUP=~/.pythonrc

_pyenv_init() {
  # python 関連の起動遅いから、ここにまとめる

  # pyenv を使う
  if  ( type "$0" > /dev/null 2>&1  ||  [[ -d  ~/.pyenv  ]] ) && [[ $TERM != screen* ]]; then
    ### pyenv
    eval "$(pyenv init -)"
    eval "$(pip completion --bash)"
    eval "$(pipenv --completion)"
    # cmd_is_exists pyenv-virtualenv && eval "$(pyenv virtualenv-init -)"
  fi
}
eval "$(lazyenv.load _pyenv_init pyenv python pip pipenv )"

screen / tmux の起動時は環境変数を引き継ぐので重複処理はしないようにしてたり。

他の似たようなコマンドはどうしたか

nvm / node

これらに関しては、bashrcで読み込む必要もないし、プロジェクト単位で使うことが前提でなので、bashrc から外した。 その後 nvm はあまり使わないことに気づいたのでnvm 自体を外した。

rbenv /ruby

これらに関しては、私が個人的に結構ruby使うのと、brewruby を使ってることもあり、rbenv 自体を外すことは諦めた。それぞれ固定バージョンへのsymlinkを作成してPATHの優先度で対応した。 rbenv init は使わずに済ませた。

phpenv / php

これらに関しては、プロジェクト単位にbashrc を作ることで対応している。

その他の解決方法。

pyenv 自体は、バージョンとコマンド名を解決するものなので、bash 用に専用の pyenv-virtualenv を作り、symlinkしてバージョンを固定。 シェルではロードして利用し、プロジェクトや環境ではプロジェクト環境ごとに pyenv を毎回ロードして使ってもいいかもしれない。

処理後の時間

1秒未満なら、まぁ許容範囲かな。500ms 切りたいとこではあるが。brew の completion 系がたくさんあるのでそれを外すのは難しいし。諦めた。 f:id:takuya_1st:20190414151325p:plain

2019/05/17 追記

pyenv init を見ていて気づいた。

pyenv init を実行してPATH を見ようと実行して気づいたのですが。

pyenv init 自体は、pyenv の別名関数を定義していて、pyenv は遅延ロード的な動作になっている。

他の処理を取り除いた bash や root の bash で pyenv init - するとすごく早い。

もしかしたらpyenv init が遅い原因は、pyenv ではなく、brew など他のパッケージにあるのかもしれない。

# pyenv init -
export PATH="/var/root/.pyenv/shims:${PATH}"
export PYENV_SHELL=sh
command pyenv rehash 2>/dev/null
pyenv() {
  local command
  command="${1:-}"
  if [ "$#" -gt 0 ]; then
    shift
  fi

  case "$command" in
  activate|deactivate|rehash|shell)
    eval "$(pyenv "sh-$command" "$@")";;
  *)
    command pyenv "$command" "$@";;
  esac
}

参考資料

GitHub - takezoh/lazyenv: lazyenv provides the delayed calling function associated with the specified commands.

ログインシェルの起動を高速化する lazyenv を作った - Qiita

Lazy load nvm for faster shell start - Broken By Me