ようへいの日々精進XP

よかろうもん

JRuby で JVM の起動時間を短縮出来ないか調査した (Nailgun を試した)

tl;dr

inokara.hateblo.jp

前回の記事で, JRubyスクリプトrspec を実行する際に JVM の起動時間の遅さがすごく気になっていたので, これを短縮する方法について調べたら Nailgun というオプションを使うと良さそうということで試したメモ.

本記事で試す環境は, 前回の記事と同様に以下のような環境を利用する.

$ java -version
openjdk version "1.8.0_171"
OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11)
OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)
$ jruby --version
jruby 9.2.0.0 (2.5.0) 2018-05-24 81156a8 OpenJDK 64-Bit Server VM 25.171-b11 on 1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11 +jit [linux-x86_64]

Nailgun とは

github.com

上記のドキュメントより引用.

In JRuby 1.3, we officially shipped support for Nailgun. Nailgun is a small library and client-side tool that reuses a single JVM for multiple invocations. With Nailgun, small JRuby command-line invocations can be orders of magnitude faster.

  • 単一の JVM を複数の呼び出しに再利用する小さなライブラリとクライアントサイドのツール
  • Nailgun を利用すると, 小さな JRubyコマンドライン呼び出しを数桁速くすることが出来る

Nailgun を利用するには, JRuby--ng-server オプションで起動しておいて, 別の端末の JRuby--ng オプションを付けて起動する.

# Nailgun サーバーを起動
$ jruby --ng-server &

# 別の端末で Nailgun モードでスクリプトを実行
$ jruby -S --ng sample.rb

試す

Hello World

以下のようなコードを用した.

p 'hello world'

これを hello_world.rb として保存して, Nailgun を利用した場合と利用しない場合で実行時間の変化を確認.

# Nailgun を使わない x 3 回
$ time jruby -S hello_world.rb 
"hello world"

real    0m2.414s
user    0m6.140s
sys     0m0.168s
$ time jruby -S hello_world.rb 
"hello world"

real    0m2.524s
user    0m6.624s
sys     0m0.184s
$ time jruby -S hello_world.rb 
"hello world"

real    0m2.441s
user    0m6.268s
sys     0m0.152s

# Nailgun を使う x 3 回
$ time jruby -S --ng hello_world.rb 

real    0m3.921s
user    0m0.032s
sys     0m0.016s
$ time jruby -S --ng hello_world.rb 

real    0m0.851s
user    0m0.016s
sys     0m0.012s
$ time jruby -S --ng hello_world.rb 

real    0m0.763s
user    0m0.016s
sys     0m0.028s

Nailgun を使わない場合, たかだか Hello world で 3 秒くらい掛かってしまう. Nailgun を利用すると, 初回は Nailgun を利用しない場合と殆ど変わらないが, 2 回目以降は 3 分の 1 程の時間で処理が完了している. 尚, Nailgun を利用した場合, Hello world が出力されていないが, これは Nailgun サーバーを起動した端末側で出力されている.

minitest

以下のような, 超簡単なテストコードを実行させてみる.

require 'minitest/autorun'

class FooTest < Minitest::Test
  def test_foo
    assert_equal 'foo', 'foo'
  end
end

これを minitest.rb として保存し, Nailgun を利用した場合と利用しない場合で実行時間の変化を確認.

# Nailgun を使わない x 3 回
$ time jruby -S minitest.rb 
Run options: --seed 58096

# Running:

.

Finished in 0.026389s, 37.8950 runs/s, 37.8950 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

real    0m3.359s
user    0m9.492s
sys     0m0.196s
$ time jruby -S minitest.rb 
Run options: --seed 9728

# Running:

.

Finished in 0.032048s, 31.2030 runs/s, 31.2030 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

real    0m3.523s
user    0m10.096s
sys     0m0.220s

$ time jruby -S minitest.rb 
Run options: --seed 24004

# Running:

.

Finished in 0.033907s, 29.4925 runs/s, 29.4925 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

real    0m3.368s
user    0m9.760s
sys     0m0.208s

# Nailgun を使う x 3 回
$ time jruby -S --ng minitest.rb 

real    0m1.699s
user    0m0.020s
sys     0m0.020s
$ time jruby -S --ng minitest.rb 

real    0m1.430s
user    0m0.024s
sys     0m0.016s
$ time jruby -S --ng minitest.rb 

real    0m1.113s
user    0m0.028s
sys     0m0.020s

RSpec

以下のような RSpec で実行可能なテストコードを書いてみた.

require 'rspec/expectations'

RSpec::Matchers.define :have_word do |expected|
  match do |actual|
    actual.include?(expected)
  end
end

RSpec.describe 'foo-bar-bar' do
  it { should have_word('foo') }
end

これを rspec.rb として保存し, 動作確認してみる.

# Nailgun を使わない x 3 回
$ time jruby -S rspec rspec.rb
... 略 ...
real    0m5.050s
user    0m14.120s
sys     0m0.248s
$ time jruby -S rspec rspec.rb
... 略 ...
real    0m4.853s
user    0m13.624s
sys     0m0.284s
$ time jruby -S rspec rspec.rb
... 略 ...
real    0m5.101s
user    0m14.404s
sys     0m0.240s

# Nailgun を使う x 3 回
$ time jruby -S --ng rspec rspec.rb
real    0m1.510s
user    0m0.012s
sys     0m0.020s
$ time jruby -S --ng rspec rspec.rb
real    0m1.795s
user    0m0.012s
sys     0m0.004s
$ time jruby -S --ng rspec rspec.rb
real    0m2.585s
user    0m0.036s
sys     0m0.020s

ぱっと見た感じだと, 何れの場合にも Nailgun を利用した方が圧倒的に処理時間は速いように見える.

以上

すごくイイこと尽くめに感じる Nailgun だが, ドキュメントには以下のように書かれている.

Nailgun seems like a magic bullet, but unfortunately it does little to help certain common cases like booting RubyGems or starting up Rails (such as when running tests). It also can't help cases where you are causing lots of sub-rubies to be launched, and if you have long-running commands the usual Control-C may not be able to shut them down on the server. Your best bet is to give it a try and let us know if it helps.

Nailgun は魔法の弾丸じゃないし, gem の起動やテストの実行時など, Railsの起動などの一般的なケースはほとんど役に立たないとあるので, 実際に利用する場合には注意が必要.

参考

github.com

tagomoris.hatenablog.com

ありがとうございました.