MRIとRubiniusで排他制御

はじめに

Matz's Ruby Interpreter(MRI)とRubiniusで排他制御したマルチスレッドプログラムを実行し、結果を比較します。
実行時間計測、CPUコア使用状況を見ていきます。

  1. MRI排他制御なし
  2. MRI排他制御あり
  3. Rubinius排他制御なし
  4. Rubinius排他制御あり

の4つを比較します。

準備
  • 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
    1000000.times do
      variable += 1
    end
  end
end

threads.each(&:join)

puts variable
variable = 0
mutex = Mutex.new

threads = 5.times.map do
  Thread.new do
    1000000.times do
      mutex.synchronize do
        variable += 1
      end
    end
  end
end

threads.each(&:join)

puts variable

メインスレッド以外にスレッドを5つ作成し、
それぞれのスレッドで百万回、足し合わせる処理を行います。
Mutexクラスのsynchronizeメソッド排他制御します。

MRIの実行結果 排他制御なし
  • 実行時間計測
$ time ruby multi_thread.rb 
5000000

real	0m0.500s
user	0m0.476s
sys	0m0.020s

はやいです。
topコマンドしても意味ないくらい一瞬です。

  • CPUコア使用状況
%Cpu0  : 14.8 us,  2.7 sy,  0.0 ni, 82.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  5.7 us,  1.7 sy,  0.0 ni, 92.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  6.7 us,  0.7 sy,  0.0 ni, 92.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  6.8 us,  0.7 sy,  0.0 ni, 92.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   3957672 total,  1806896 used,  2150776 free,   100764 buffers
KiB Swap:  4104188 total,        0 used,  4104188 free,   864556 cached

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

MRIの実行結果 排他制御あり
  • 実行時間計測
$ time ruby synchronized_multi_thread.rb 
5000000

real	0m57.339s
user	0m30.708s
sys	1m20.836s

遅くなりました。
後述のRubinius排他ありと同じくらい時間かかります。
システム時間に大分かかってますね。

  • CPUコア使用状況
Tasks: 232 total,   1 running, 231 sleeping,   0 stopped,   0 zombie
%Cpu0  : 16.2 us, 36.9 sy,  0.0 ni, 46.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 17.5 us, 32.8 sy,  0.0 ni, 49.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 11.5 us, 31.0 sy,  0.0 ni, 57.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 13.8 us, 30.5 sy,  0.0 ni, 55.3 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
KiB Mem:   3957672 total,  1791912 used,  2165760 free,   100168 buffers
KiB Swap:  4104188 total,        0 used,  4104188 free,   858356 cached
Rubiniusの実行結果 排他制御なし
  • 実行時間計測
$ time rbx multi_thread.rb
2344231

real	0m1.903s
user	0m6.032s
sys	0m0.044s

MRI排他制御なしより遅いです。
競合してます。

  • CPUコア使用状況
Tasks: 234 total,   1 running, 233 sleeping,   0 stopped,   0 zombie
%Cpu0  : 46.3 us,  1.7 sy,  0.0 ni, 52.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 48.3 us,  1.0 sy,  0.0 ni, 50.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 60.1 us,  1.3 sy,  0.0 ni, 38.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 48.2 us,  0.3 sy,  0.0 ni, 51.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   3957672 total,  1886912 used,  2070760 free,   101468 buffers
KiB Swap:  4104188 total,        0 used,  4104188 free,   890144 cached

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

Rubiniusの実行結果 排他制御あり
  • 実行時間計測
$ time rbx synchronized_multi_thread.rb
5000000

real	0m54.607s
user	0m46.080s
sys	1m9.592s

MRI排他制御ありと同じくらい時間かかりました。
競合もしていません。

  • CPUコア使用状況
Tasks: 234 total,   1 running, 233 sleeping,   0 stopped,   0 zombie
%Cpu0  : 23.4 us, 30.9 sy,  0.0 ni, 45.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 22.0 us, 33.2 sy,  0.0 ni, 44.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 18.6 us, 28.8 sy,  0.0 ni, 52.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 18.6 us, 27.0 sy,  0.0 ni, 54.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   3957672 total,  1991948 used,  1965724 free,   102420 buffers
KiB Swap:  4104188 total,        0 used,  4104188 free,   904304 cached

排他制御していない時と比べると活用率は下がりますね。

まとめ
  • 排他制御ありの場合はMRIとRubiniusで同じくらいの実行時間になる
  • 排他制御しないと期待通りの結果を得られない場合があるので、そもそもやばい