るびー めも

Ruby の学習メモを記す

Ruby で Thread を使う(その1)

suetotanuki さんに触発されて、Ruby + Thread を試してみました。
http://sutetotanuki.hatenablog.com/entry/2013/05/12/201534

以下のあたりの資料が、すごく参考になりました。

CRubyのロックデザインの解説および改善案について
http://rubykaigi.org/2011/ja/schedule/details/17S10
#vimeo は必見ですね。

第19章 スレッド
http://i.loveruby.net/ja/rhg/book/thread.html
#このあたりの資料も、軽く読みました。

何となくですが、今の実装だと複数コアに股がった
並列処理は出来そうになさそう。

RubyのMVM APIの共同研究が始まる
http://www.infoq.com/jp/news/2008/03/ruby-mvm-research
#こんな記事もあるし、
#いずれ、VM が並列で走行できる日が来るかもしれませんが、、、

suetotanuki さんのソースコードを、CoreDuo Macbook で走行させてみました。
#理由は、Core i5Macbook Air だとCPU Usage が100% 超えてたから。
#多分、HyperThreading のせいだと思うんだけど、HT OFF の方法がわからん

$ ruby -v
ruby 1.9.3p392 (2013-02-22 revision 39386) [i386-darwin10.8.0]
$ cat tp.rb
def fib(n)
  n < 2 ? n : fib(n-1) + fib(n - 2)
end

th1 = Thread.new {
  sleep 1
  100.times {
    fib(1000)
  }
}

th2 = Thread.new {
  sleep 1
  100.times {
    fib(1000)
  }
}

th1.join; th2.join;
$ ruby ./tp.rb &
[1] 329
$ top -pid 329

...


Processes: 61 total, 3 running, 58 sleeping, 250 threads 23:34:39
Load Avg: 0.80, 0.40, 0.20 CPU usage: 58.25% user, 4.58% sys, 37.15% idle
SharedLibs: 180M resident, 5008K data, 35M linkedit.
MemRegions: 6110 total, 295M resident, 17M private, 201M shared.
PhysMem: 207M wired, 434M active, 102M inactive, 743M used, 1303M free.
VM: 42G vsize, 460M framework vsize, 45413(0) pageins, 0(0) pageouts.
Networks: packets: 13087/6633K in, 9374/759K out.
Disks: 8123/523M read, 3442/66M written.

PID COMMAND %CPU TIME #TH #WQ #POR #MRE RPRVT RSHRD RSIZE VPRV
329 ruby 99.3 00:48.80 4/1 0 36 46 1836K 264K 3504K 29M

こんな感じで、CPU Usage が 100% を超えることはなかった。

ただ、Ruby の Thread モデルは使い方次第だと思います。

以下のように、sleep して I/O 待ちにちょくちょくなるような
プログラムだと Thread の恩恵があると思います。

$ cat parallel.rb 
#!/usr/bin/env ruby
count = 10 
def func(id, count)
  i = 0;
  while (i < count)
    #puts "Thread #{i} Time: #{Time.now}"
    sleep(0.1)
    i = i + 1
  end
end
 
puts "Started at #{Time.now}"
thread1 = Thread.new{func(1, count)}
thread2 = Thread.new{func(2, count)}
thread3 = Thread.new{func(3, count)}
thread4 = Thread.new{func(4, count)}

thread1.join
thread2.join
thread3.join
thread4.join
puts "Ending at #{Time.now}"

実行すると、以下の結果

$ time ruby ./parallel.rb 
Started at 2013-05-13 23:40:09 +0900
Ending at 2013-05-13 23:40:10 +0900

real	0m1.224s
user	0m0.106s
sys	0m0.093s
$ 

sleep する度に、thread が切り替わって並列で走行しているので
real で 1.2 sec という実行結果。

$ cat serial.rb 
#!/usr/bin/env ruby
count = 10 
def func(id, count)
  i = 0;
  while (i < count)
    #puts "Thread #{i} Time: #{Time.now}"
    sleep(0.1)
    i = i + 1
  end
end
 
puts "Started at #{Time.now}"
func(1, count)
func(2, count)
func(3, count)
func(4, count)

puts "Ending at #{Time.now}"
$ 
$ time ruby ./serial.rb 
Started at 2013-05-13 23:41:01 +0900
Ending at 2013-05-13 23:41:05 +0900

real	0m4.241s
user	0m0.105s
sys	0m0.093s
$ 

こちらは、馬鹿正直に4回繰り返しているので
4.2 sec ほど必要です。

まぁ、私は Ruby レベル低すぎて Thread 使える人間ではございません。