MRIとRubiniusでマルチスレッド

はじめに

Matz's Ruby Interpreter(MRI)とRubiniusでマルチスレッドプログラムを実行し、結果を比較します。
実行時間計測、CPUコア使用状況を見ていきます。
複数回実行して、平均を取るなど真面目に検証はしていません。

準備
  • Rubyのバージョン
$ ruby -v
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux]
  • Rubiniusのバージョン
$ rbx -v
rubinius 2.2.6.n110 (2.1.0 c004ced8 2014-04-20 JI) [x86_64-linux-gnu]

Rubiniusのインストールはこちらから
http://rubini.us/doc/ja/getting-started/building/

  • 検証に用いるプログラム
variable = 0

threads = 5.times.map do
  Thread.new do
    100000000.times do
      variable += 1
    end
  end
end

threads.each(&:join)

puts variable

メインスレッド以外にスレッドを5つ作成し、
それぞれのスレッドで1億回、足し合わせる処理を行います。
スレッドセーフではありません。

Rubyの実行結果
  • 実行時間計測
$ time ruby multi_thread.rb 
500000000

real	0m43.034s
user	0m42.688s
sys	0m0.052s

MRIはGVLが実装されているので、競合状態は発生しないですね。

  • CPUコア使用状況
Tasks: 235 total,   1 running, 234 sleeping,   0 stopped,   0 zombie
%Cpu0  : 45.6 us,  0.3 sy,  0.0 ni, 54.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  5.4 us,  0.7 sy,  0.0 ni, 94.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 20.3 us,  2.3 sy,  0.0 ni, 77.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 41.6 us,  0.3 sy,  0.0 ni, 58.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   3957672 total,  2745588 used,  1212084 free,   220392 buffers
KiB Swap:  4104188 total,        0 used,  4104188 free,  1298312 cached

topコマンドから1を押すとコアごとのCPU使用率を見ることができます。

Rubiniusの実行結果
  • 実行時間計測
$ time rbx multi_thread.rb
198985546

real	6m1.643s
user	22m39.488s
sys	0m1.408s

MRIと比較すると遅いです。
競合しまくりです。
スレッドセーフでないプログラムを作成したかいがありました。
マルチスレッドの場合、user値は合計値になるのでreal値よりも大きくなることがあるのですね。

  • CPUコア使用状況
Tasks: 234 total,   1 running, 233 sleeping,   0 stopped,   0 zombie
%Cpu0  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 99.7 us,  0.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 99.7 us,  0.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   3957672 total,  2120944 used,  1836728 free,   205068 buffers
KiB Swap:  4104188 total,        0 used,  4104188 free,   998288 cached

コアをたくさん使用してます。

まとめ
  • MRIのほうがはやい
  • Rubiniusはコアをフル活用してくれた(が、遅い)
  • MRIはGiant VM lockを実装しているので、同時に実行されるネイティブスレッドは常にひとつのため、このプログラムでは競合状態を発生できなかった