bundle install や gem install の何で時間がかかるのか

Shimpei Makimoto
4 min readFeb 27, 2018

--

お仕事で Ruby をお使いのみなさんの多くが、bundle install や gem install をしたすぐ後にオフィス近隣のターバックスやお手洗に行ったりすると思います。

最近パッケージマネージメントに興味が出てきて RubyGems や Bundler のコードを読んでいるのですが、結局動かしてみないとよく分からないし、動かしてみたらみたで何度も bundle install したりしていたら無限に時間が吸われるので、結局どこが遅いのか調べてみました。

bundle install のプロファイル

というこでお馴染な方にはお馴染な stackprof を使ってプロファイルを取ります。

まず、以下のようなプロファイラを書きます。計測できるようにコード内で実行する方法を探るために CLI まわりを読み込む必要があってやや面倒でした。

https://gist.github.com/makimoto/ce8aab25e03ba212d672226200c3bbf4

Ruby は 2.6.0.preview1、bundler は執筆時最新の v1.16.1 、TestGemfile として rails/rails の Gemfile を持ってきました。

モジュールやメソッドが多すぎて何が何やらだと思うので依存関係の図も出力してみました。

Profile Report for `bundle install` execution

実行コストがかかっている上位はファイル操作系が占めていて中でも Gem::Package#realpath がトップでした、これは実態は File.realpath で、Gem::Package#mkdir_p_safe というメソッドなどを経由して gem パッケージ内のすべてのファイルに対して1回以上呼ばれているようです。

あとはファイルをダウンロードしたり、パッケージファイルを展開したりといったいかにも時間がかかりそうなことが支配的でした。まあ、予想通りですね。

あと、 RubyGems.org の API に問い合わせたりも時間的コストはかかっています。このあたりは API 側の実装などもかかわってきそうですが、RubyGems.org の API まわりについては執筆時点ではまだままり読み込めてない感じです。

さて、ファイル操作や通信などいかにも時間がかかりそうな処理を除外すると、次に出てくるのが依存関係を解決する関係の処理です。今回はほぼまっさらな環境で bundle install するという実験条件にしたのですが、ある程度使い込んだ環境だと日常的にはこちらがやっかいになってきます。 (スターバックスに行くほど待つことはないけど、オフィスのお菓子コーナーに行ったりしそう)

気になるのは、RubyGems と Bunlder との両方で依存解決をしているので、そのあたりで無駄が生じていそうです。 (別のソフトウェアなので、別々にやるのは道理ではあるのだけど)

Bundler は依存解決のために Molinillo というライブラリを同梱していて、これがそこそこかしこそうでした。

ちなみに gem install についても同じようなプロファイルを取ったのですが、傾向は同じだったので割愛します。

ということで

bundle install とか gem install とかの何が遅いのかというのを大雑把に調べました。

支配的なのはファイル操作 (ダウンロードしたり展開したり) と通信 (RubyGems.org の API と話したり) でした。まあ大方の予想通りですね。

依存関係はまっさらな環境ではほとんど影響ない感じですが、もっと使い込んだ環境でどう影響があるのかはもうちょっと調べた方が良さそうです。調べます。

あと、これは特に期待していなかった効果なのですが、 Graphviz で stackprof の結果を出力するといい感じにロジックの依存関係の図を作ってくれるので、大きめのコードベースを読むとき、まず図を出力すると参考になると分かりました。便利。

調べてばっかりというのもアレなので、このあたりの知見を使って速くなりそうなところは Bundler や RubyGems に還元したいところ。

やっていきましょう。

--

--