これは何?

オリジナルのRuby VMの実装(CRuby, MRI)は大域VMロック(Giant VM Lock; GVL)を用いています。GVLのおかげでVMと拡張ライブラリの開発者は並列プログラミングについて考える必要が無く実装が容易になります。しかしそのせいで、Rubyでマルチスレッドプログラムを書いても同時に一つのスレッドしか実行されないという性能上の問題がありました。一方、近年のCPUにはハードウェアトランザクショナルメモリ(Hardware Transactional Memory; HTM)と呼ばれる新しい機能が実装されています。HTMをロックの代わりに用いると、開発者は並列プログラミングについてほとんど考える必要が無いまま、同時に複数のスレッドが動作可能になり性能問題が解決する、と言われています。我々はRuby VMのGVLをHTMで置き換えた実験を行い、PPoPP 2014で発表しました。英語の論文と発表資料はこちらにあります。また、内容は少し古いですが日本語の発表資料はこちら(PDF)にあります。

このページでは実験で用いたコードをRuby VMに対するパッチとして公開しています。

履歴

2015年7月29日: Eメールアドレスを修正
2015年5月24日: IBM USへの移動に伴いページを新規作成
2014年9月18日: バグを修正、RUBY_DISABLE_HTMを導入
2014年9月8日: 修正パッチを適用(contributed by Masahiro Ide)
2014年6月24日: パッチとビルド方法を公開

パッチとビルド方法

我々はruby-1.9.3-p547に対するパッチを公開しています。パッチはこちらからダウンロードしてください。 また、ruby-1.9.3-p547は以下のリンクからダウンロードできます。https://www.ruby-lang.org/ja/news/2014/05/16/ruby-1-9-3-p547-released/ 我々のパッチはruby-1.9.3の他のパッチレベルに対しても(確認はしていませんが)少しの修正で適用できるはずです。

ソースツリーを展開した後、以下のようにパッチを適用してください。

> cd ruby-1.9.3-p547
> patch -p1 < $PATH_TO_YOUR_PATCH/ruby_htm_gvl-1.9.3-p547_v3.patch

パッチ適用後、*.incファイルを不要に再生成しないようにtouchしておくことをお勧めします。

> touch *.inc

configureスクリプトに--enable-htm-gvlオプションを付けてからビルドしてください:

> ./configure --enable-htm-gvl
> make
# make install

実行方法

ビルドされたRubyのバイナリはデフォルトでGVLにHTMを用います。実行するにはIBM POWER HTMかIntel TSXを実装したCPUが必要です。パッチはAIX/POWER8とLinux/Intel Haswellプロセッサ上でテストしています。パッチにはIBM System z(メインフレーム)のHTMのためのコードが一部含まれていますが、Ruby自体がSystem zでは現状動きません。

HTMはマルチスレッドのRubyプログラムでのみ用いられます。シングルスレッドのRubyプログラムではGVLの方が速いのでGVLが用いられます。マルチスレッドプログラムでも常にGVLを用いたい場合は実行時オプションとして--disable-htmを指定するか環境変数RUBY_DISABLE_HTMをyesにセットしてください。

> ruby --disable-htm your_app.rb
> export RUBY_DISABLE_HTM=yes; ruby your_app.rb

HTMとGVLの挙動をチューニングするために、環境変数で指定できるオプションがいくつか用意されています。ただしデフォルトの値は既にチューニングされているので通常は値を変更する必要はありません。

環境変数名短縮名説明有効な値デフォルトの値
RUBY_HTM_SCHED_INTERVAL RH_INTV トランザクション長。詳細はPPoPP 2014の論文を参照してください。正の整数値は固定トランザクション長を意味します。負の整数値はトランザクション長の動的な調整を有効化し、初期値は指定された負の整数値の絶対値となります。例えばRH_INTV=-255はトランザクション長を255で初期化し、アボート統計に応じて動的にトランザクション長を短くしていきます。 整数:-255以上-1以下、または1以上 -255
RUBY_HTM_TX_SIZE_RECALC_THRESHOLD RH_RECALC ターゲットとするアボート率。RH_INTVに負の値が指定された時のみ有効。 浮動小数点数: [0.0, 100.0] 6.0
RUBY_HTM_PERSISTENT_RETRY_MAX RH_PRETRY 何回persistentアボートが起きてからGVLを獲得するか。 正の整数 3
RUBY_HTM_TRANSIENT_RETRY_MAX RH_TRETRY 何回transientアボートが起きてからGVLを獲得するか。 正の整数値 3
RUBY_HTM_GVL_RETRY_MAX RH_GRETRY 他のスレッドがGVLを獲得している場合に、何回GVL解放を待ってリトライしてからGVLを獲得するか。 正の整数 16
RUBY_GVL_SPIN_MAX RB_GSPIN GVLが何回スピンウェイトしてからPthread条件変数で寝るか。 正の整数 100000000
RUBY_PRINT_HTM_STATS RH_STATS 実行の最後にHTMの統計を出力する。 どんな値でも可 {指定されず}

また、直接HTMやGVLとは関係しませんが、Rubyのヒープが縮むことを防ぐオプションが提供されます。我々の論文の実験ではGCの頻度を下げるためにRUBY_HEAP_MIN_SLOTSでRubyヒープの初期サイズを拡張しましたが、このオプションが無いとRubyヒープが急速に縮んでしまいます。

環境変数名短縮名説明有効な値デフォルトの値
RUBY_DO_NOT_SHRINK_HEAP なし Rubyヒープを縮めない。 どんな値でも可 {指定されず}

PPoPP論文の結果の再現方法

以下の環境変数をセットしてください。

> export RH_STATS=yes
> export RUBY_HEAP_MIN_SLOTS=10000000
> export RUBY_DO_NOT_SHRINK_HEAP=yes

HTM-1, -16, -256のためにはRH_INTVをそれぞれ1, 16, 256にセットしました。HTM-dynamicのためにはRH_INTVを-255(デフォルト値)にセットしました。 “GIL”のためには--enable-htm-gvlをconfigureスクリプトに指定せずにビルドしたバイナリを用いました。

Ruby版のNAS Parallel Benchmarks 3.0はこちらからダウンロードできます: http://www-hiraki.is.s.u-tokyo.ac.jp/members/tknose/npb3.0-ruby.tar.gz

以下のコマンドで実行できます:

> ruby -I . BT.rb -CLASSS -np4

WEBrickはRubyに添付して配布されています。我々はWEBrickベースの非常に単純なHTTPサーバを用いました。

require 'webrick'
include WEBrick
ARGV[0] ||= 8008
s = HTTPServer.new(:Port => ARGV[0].to_i,
                   :MaxClients => 1000,
                   :Logger => Log.new($stderr, BasicLog::ERROR),
                   :DocumentRoot => File.join(Dir::pwd, "public_html"),
                   :AccessLog => [[ File.open("log.txt", "w"), AccessLog::COMMON_LOG_FORMAT]]
                   )
trap("INT") {
  s.shutdown
}
s.start

Ruby on Railはバージョン4以降はデフォルトでスレッドセーフとなり並列実行されますが、WEBrickだけは特別扱いして並列実行されません。我々はRuby on Railsのソースコードを以下のリンク先にあるように修正して、WEBrickでも並列実行されるようにしました: https://github.com/rails/rails/issues/10772

ライセンス

我々のパッチのライセンスは元のRubyのライセンスと同一です:https://www.ruby-lang.org/en/about/license.txt

バグレポートとコメント

大平怜(rodaira [at] us . ibm . com)まで。Twitter: @ReiOdaira