LoginSignup
29
24

More than 5 years have passed since last update.

bundlerのstandaloneモードでプログラムの起動時間を速くする

Last updated at Posted at 2015-08-06

Rubyで書いたスクリプトの起動時間を速くした話。

tl;dr

  • bundle execもしくはbundle install --binstubsで生成して動かす実行ファイルはそこそこ遅い
  • 一方、bundlerには bundle install --standaloneというオプションがあり、実行時にbundler無しにversion lockしたgemを使う方法を提供する仕組みで、bundle install --standalone すると bundle/bundler/setup.rb が生成される。
  • bundle/bundler/setup.rb にはGemfile.lockに従ったバージョンのgemのlibのパスが$LOAD_PATHにpushされるコードが書かれているので、bundlerに含まれる 'bundle/setup' を require 'bundle/setup'する代わりに bundle/bundler/setup.rb をloadする方が起動は速くなる

きっかけ

rubyで書いた監視スクリプトの起動が遅く、どうやらrequire 'bundle/setup'がそこそこ遅いということが分かって、では早くする方法がないか調てみた

bundlerのstandaloneモード

bundle install --standaloneすると以下が行われる

  • `project-dir'/bundle/ruby// 以下にGemfileに書いたgemがインストールされる
  • `project-dir'/bundle/bundler/setup.rb が生成される

例えば、Gemfileにgem 'sshkit'と書いて bundle install --standaloneすると`project-dir'/bundle/bundler/setup.rb は次のファイルが生成されるので、だいたいどういう機能か分かるとおもう。

bundle/bundler/setup.rb
require 'rbconfig'
# ruby 1.8.7 doesn't define RUBY_ENGINE
ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
ruby_version = RbConfig::CONFIG["ruby_version"]
path = File.expand_path('..', __FILE__)
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/colorize-0.7.7/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/net-ssh-2.9.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/net-scp-1.2.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sshkit-1.7.1/lib"

bundle execやrequire 'bundle/setup'する代わりに↑を実行時にloadすれば確かにGemfile.lockでロックされたバージョンが使われるので早い

計測

どのくらい速くなるか計測した

Gemfileを用意。gemはテストコードでは使わないのでなんでもよいのでとりあえずsshkit入れとく。

Gemfile
 $ cat Gemfile
source "https://rubygems.org"
gem 'sshkit'

以下のパターンで実行時間を計測した。全てrbenv配下です。

  • ruby -e '' 素のruby
  • bundle exec ruby -e ''
  • binstub化された実行スクリプト
  • standalone化された実行スクリプト

ruby -e ''

 $ time ruby -e ''

real    0m0.115s
user    0m0.063s
sys     0m0.050s

bundle exec ruby -e ''

 $ time bundle exec ruby -e ''

real    0m0.542s
user    0m0.418s
sys     0m0.115s

binstub

以下のテストスクリプトを作成。内容はbundle install --binstubsで生成される実行ファイルを参考に。

bin/binsutb
require 'pathname'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
  Pathname.new(__FILE__).realpath)

require 'rubygems'
require 'bundler/setup'

実行結果。bundle execするよりは多少速くなったけど、まだ結構遅い。

$  time ruby bin/binstub

real    0m0.386s
user    0m0.258s
sys     0m0.121s

standalone

bundle install --standaloneした後、以下のスクリプトを作成。

bin/standalone
require_relative '../bundle/bundler/setup'

実行結果。確かに速くなった! bundle execと比べると実に0.4秒の差

$ time ruby bin/standalone

real    0m0.110s
user    0m0.063s
sys     0m0.042s

デメリット

ちなみにデメリットもある。 --standaloneオプションは.bundle/configに記録されないので、installする度に--standaloneを生やす必要がある。

またBundler.requireが使えなくなるので、例えばRailsでこれやろうとすると動かないので、なんらかうまくやる必要がある。まあRailsみたいにプロセスをdaemon化する場合はstandalone化する必要はなさそう。

まとめ

--standaloneで実行時間を速くした。実行時間は bundle exec > binstub > standalone の順になった。

RailsなどWebサーバなどプロセスをdaemon化するケースでは起動時間は気にならないので良いですが、監視スクリプトなど実行頻度が高い場合はstandalone化したほうが良い。

自分の場合、監視サーバでの監視スクリプトの実行にstandaloneモードを使ってます。なお、deployにcapistrano-bundle_rsyncを使っておりSupport bundle install with --standalone option のpull requestを送って--standalone対応となりました。

参考

Faster Test Boot Times with Bundler Standalone の記事で知りました。この記事ではrspecの起動を速くしたかったようです。

29
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
24