zshの起動が遅いのでjEnvからasdfに乗り換える
概要と結論
- 🤔 macOSでzshの起動が遅いことが気になっていた
- 😲
zprof
でボトルネックを調べると_jenv_export_hook
がヒット - 🎉 jEnvからasdfに乗り換えると起動時間が2.2秒から1.2秒弱に半減
といったことを試した記録です。
zshの起動が遅いのでなんとかする
このところzshの起動が遅いのが気になっていました。起動時間が遅いと
- 待機中にブラウザを開くなど別作業を始める → なにしてたっけ?
- じっと待機してからコマンドを打ち始める → タイプミスが増加する(気がする)
ような問題が発生します。どちらも別問題として対処できそうな気もしますが、それはそれとしてモヤモヤの原因は絶たねばなりません。短気はプログラマーの美徳ですから[1]。
まずはzshの起動時間を計測してみます。私自身の備忘録として、それぞれのオプションの意味もまとめておきます。
$ time zsh -i -c exit
zsh -i -c exit 0.18s user 0.28s system 20% cpu 2.223 total
オプション | 意味 |
---|---|
-i |
インタラクティブシェルを起動する。コマンドを入力できる状態で起動するということ |
-c |
続く文字列をコマンドとして実行する。exit なら起動して即座に終了するということ |
この 2.223 total
の部分がコマンドが開始してから終了までの経過時間ですね。体感3秒くらいなのでこんなものでしょう。ちなみに実行環境は次のとおりです。
$ neofetch --off
OS: macOS 14.0 23A344 arm64
Host: Mac14,2
Kernel: 23.0.0
Shell: zsh 5.9
Terminal: iTerm2
CPU: Apple M2
Memory: 3174MiB / 16384MiB
ボトルネックを探る
こちらの記事を参考にしてzprof
でボトルネックを探ります。
.zshenv
の最初と.zshrc
の最後に以下を加えて実行します。
# ファイルの最初に記載する
zmodload zsh/zprof && zprof
# ファイルの最後に記載する
if (which zprof > /dev/null 2>&1) ;then
zprof
fi
その結果、処理時間の上位5個は以下でした。細かい見方はわかりませんが、きっとcalls
が呼び出し回数ということでしょう。たとえば_omz_source
は24回呼び出されていますが、74.02
なので、3列目が処理全体の実行時間ということになりそうです。
num calls time self name
-----------------------------------------------------------------------------------
1) 1 319.20 319.20 60.90% 319.20 319.20 60.90% _jenv_export_hook
2) 24 74.02 3.08 14.12% 60.14 2.51 11.47% _omz_source
3) 4 38.31 9.58 7.31% 38.31 9.58 7.31% compaudit
4) 2 72.03 36.02 13.74% 33.72 16.86 6.43% compinit
5) 1 22.02 22.02 4.20% 22.02 22.02 4.20% zrecompile
ボトルネックとなっている_jenv_export_hook
は明らかにjEnvのものですね。
こいつをアンインストールして60.90%
をなんとかしてやろう、というのが今回の趣旨です。
補足:jEnvについて
jEnvからasdfに乗り換える
ということで、jEnvをアンインストールして別のツールに乗り換えます[3]。以下の理由から、今回の乗り換え先はasdfです。
- anyenvより軽いらしい
- Bitriseの仮想マシンにプリインストールされているので仲良くなっておきたかった
ただ、これで遅くなったとしても別にJavaは大して使わないし、ダメなら適当なものをグローバルインストールしておけばいいや、というテンションで進めます😬
補足:asdfについて
汎用的なバージョン管理ツールです。jEnvだけでなくpyenvやnodenvのような〇〇envをひとまとめにしたようなものです。プラグインが見つかる限り何でもOKなツールです。
jEnvをアンインストールする
jEnvをアンインストールするため、まずはjenv versions
で紐づいているJavaを確認します。
$ jenv versions
system
11.0.15
17.0
* 17.0.5 (set by (/Users/username/.jenv/version)
openjdk64-11.0.15
temurin64-17.0.5
久しぶりに触るので、そもそもどこにインストールされているか確認するところから始めます😇
jEnvに登録されたJavaは $HOME/.jenv/versions
にシンボリックリンクとしてまとめられます。まずはこれを確認しましょう。
$ ls -classify $HOME/.jenv/versions
total 0
9278947 0 drwxr-xr-x@ 12 username staff 384 2 3 2023 ./
9278913 0 drwxr-xr-x@ 18 username staff 576 2 3 2023 ../
9286652 0 lrwxr-xr-x@ 1 username staff 39 1 3 2023 11.0.17@ -> /opt/homebrew/Cellar/openjdk@11/11.0.17
11727628 0 lrwxr-xr-x@ 1 username staff 39 1 29 2023 11.0.18@ -> /opt/homebrew/Cellar/openjdk@11/11.0.18
9279937 0 lrwxr-xr-x@ 1 username staff 62 1 3 2023 17.0.5@ -> /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home
9279939 0 lrwxr-xr-x@ 1 username staff 62 1 3 2023 17.0@ -> /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home
9286650 0 lrwxr-xr-x@ 1 username staff 39 1 3 2023 openjdk64-11.0.17@ -> /opt/homebrew/Cellar/openjdk@11/11.0.17
11727626 0 lrwxr-xr-x@ 1 username staff 39 1 29 2023 openjdk64-11.0.18@ -> /opt/homebrew/Cellar/openjdk@11/11.0.18
9286491 0 lrwxr-xr-x@ 1 username staff 62 1 3 2023 temurin64-17.0.5@ -> /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home
12296642 0 lrwxr-xr-x@ 1 username staff 59 2 3 2023 11.0.15@ -> /Applications/Android Studio.app/Contents/jbr/Contents/Home
9286654 0 lrwxr-xr-x@ 1 username staff 39 1 3 2023 11.0@ -> /opt/homebrew/Cellar/openjdk@11/11.0.17
12296640 0 lrwxr-xr-x@ 1 username staff 59 2 3 2023 openjdk64-11.0.15@ -> /Applications/Android Studio.app/Contents/jbr/Contents/Home
散らかっていて恥ずかしいのですが、11.0.15
, openjdk64-11.0.15
はいずれもAndroid Studio内のものでした。これらはそのままにするものとして、それ以外のものを片付けることにします。
どうやらopenjdk
はHomebrewからインストールされたもののようです。これを確認して削除します。
$ brew list | grep openjdk
openjdk
openjdk@11
openjdk@17
$ brew uninstall openjdk@11 openjdk@17
Uninstalling /opt/homebrew/Cellar/openjdk@11/11.0.20.1... (667 files, 296MB)
Uninstalling /opt/homebrew/Cellar/openjdk@17/17.0.8.1... (635 files, 305MB)
一方、バージョンのないopenjdk
はGradle経由でインストールされたものでした。正直Gradleも使っていないので削除します。また必要になったら入れればよいのです。
$ brew uninstall openjdk # 直接アンインストールしようとすると怒られる
Error: Refusing to uninstall /opt/homebrew/Cellar/openjdk/21
because it is required by gradle, which is currently installed.
You can override this and force removal with:
brew uninstall --ignore-dependencies openjdk
$ brew uninstall gradle # 先にgradleからアンインストールする
Uninstalling /opt/homebrew/Cellar/gradle/8.4... (20,568 files, 423.7MB)
$ brew uninstall openjdk
Uninstalling /opt/homebrew/Cellar/openjdk/21... (600 files, 331MB)
$ brew list | grep openjdk # 何も表示されない
$ ls /opt/homebrew/Cellar | grep openjdk # 何も表示されない
ということできれいになりました。$HOME/.jenv/versions
にはまだシンボリックリンクが残っていますが、あとでディレクトリごと削除するので無視します。
あとは /Library/Java/JavaVirtualMachines
のJavaを削除します。これ覚えがないのですが .pkgなどからインストールしたのでしょうか…。
$ sudo rm -rf /Library/Java/JavaVirtualMachines/temurin-17.jdk
Password:
jenv: version `17.0.5' is not installed
このJavaが登録されたjEnvから怒られるようになりました。これでOKですね。あとはAndroid Studio由来のものしか残っていないので、いざjEnv自体をアンインストールします。ホームディレクトリに残っている隠しファイルも忘れずに消しましょう。
$ brew uninstall jenv
Uninstalling /opt/homebrew/Cellar/jenv/0.5.6... (86 files, 78KB)
$ rm -rf ~/.jenv
.zshrc
にもjEnvの記述が残っているので削除します。
# 以下の記載があれば削除する
export PATH="$HOME/.jenv/bin:$PATH"
echo 'eval "$(jenv init -)"
ここまででjEnvのアンインストールは完了です。ここで一度実験してみます。
$ time zsh -i -c exit
zsh -i -c exit 0.14s user 0.17s system 26% cpu 1.155 total
2.223から半分くらいになりました。体感的にもかなり速くなっています!
asdfの環境を整える
続いてasdfを導入します。インストールはHomebrewから行います。
brew install asdf
公式ドキュメントを参考にしてasdf.sh
を.zshrc
に追加します。
echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc
Javaのプラグインasdf-javaをインストールします。
$ asdf plugin add java
Plugin named java already added
適当なJavaをインストールしておきます。私の利用範囲ではv11系以上であれば問題ないのですが、せっかくなのでopenjdk-21.0.1
を入れてみます[4]。
# バージョンを確認
$ asdf list-all java | grep openjdk
(省略)
openjdk-21
openjdk-21.0.1
$ asdf install java openjdk-21.0.1
##################################### 100.0%
openjdk-21.0.1_macos-aarch64_bin.tar.gz
openjdk-21.0.1_macos-aarch64_bin.tar.gz: OK
これだけでは設定されないので、インストールされたJavaをグローバルに指定します。
$ asdf current
java ______ No version is set. Run "asdf <global|shell|local> java <version>"
$ asdf global java openjdk-21.0.1
$ asdf current # asdfに反映されていることを確認
java openjdk-21.0.1 /Users/username/.tool-versions
$ java -version # 一度シェルを再起動してから実行
openjdk version "21.0.1" 2023-10-17
OpenJDK Runtime Environment (build 21.0.1+12-29)
OpenJDK 64-Bit Server VM (build 21.0.1+12-29, mixed mode, sharing)
大丈夫そうですね!
あとはJAVA_HOME
を指定します。これにはasdf-javaにあるコードを記載すればOKです。zshの場合は以下を~/.zshrc
に記載します。.
はsource
の意味なのでどちらでも大丈夫です。
. ~/.asdf/plugins/java/set-java-home.zsh
$ echo $JAVA_HOME
/Users/username/.asdf/installs/java/openjdk-21.0.1
$ /usr/libexec/java_home
/Library/Java/JavaVirtualMachines/openjdk-21.0.1/Contents/Home
こちらもいい感じですね。それぞれパスは異なりますが、後者は前者のシンボリックリンクなので実態は同じものです。
速度はどうなった?
この状態で起動速度を測ってみます。
$ time zsh -i -c exit
zsh -i -c exit 0.14s user 0.17s system 26% cpu 1.161 total
asdfの処理が追加されましたが、ほとんど変わらない結果になりました。無視できる程度の処理ということですね。体感的にも快適になったので満足です🎉
-
有名なので出典はいろいろ出せますが、私は『プリンシプルオブプログラミング 3年目までに身につけたい一生役立つ101の原理原則』で学びました。 ↩︎
-
jEnvはJavaのインストール自体は自分で行い、それを登録するというステップが必要です。インストールもまとめて行いたいならSDKMAN!が簡単です。過去の自分に伝えたいです。 ↩︎
-
このフックがやたらと遅いというのはGitHubのIssuesにも対処法とともに上がっていましたが、もう面倒くさいので削除します。 ↩︎
-
最近LTSが出たのでちょっと試してみたかったのです。ところでopenjdkの11系は出てこないようでした。サポート期間は
October 2024
になっているのですが、なぜなのでしょうか?🤔 ↩︎
Discussion