dRubyで並列処理

(※12月の1日から25日まで、日替わりで Ruby の Tips を紹介するイベント、 Ruby Advent Calendar jp: 2009 の 11 日目です。昨日は no6v さんでした。明日は id:willnet さんの予定です。)


RubyのThreadは時分割なので並列処理を行いたいときにちょっと困ります。そんなときにはdRubyを使ってみるのはいかがでしょうか。


http://gist.github.com/250118

#!/usr/bin/ruby
require 'drb/drb'
class MonteCarlo
  def initialize(seed)
    srand(seed)
  end
  def dice(n)
    best = rand
    n.times do
      r = rand
      best = r if r < best
    end
    best
  end
end

PROCESSORS = (ARGV[0] || 1).to_i

pids = []
workers = PROCESSORS.times.map do |i|
  uri = "druby://localhost:#{12345 + i}"
  pids << fork { DRb.start_service(uri, MonteCarlo.new(i)); sleep }
  DRbObject.new_with_uri(uri)
end

begin
  n = 5000000 / PROCESSORS
  q = Queue.new
  sleep 0.1
  ts = workers.map do |foo|
    Thread.start(foo) {|f| q.push(f.dice(n))}
  end
  ts.each{|t| t.join}
  p q.size.times.map{q.pop}.min
ensure
  pids.each {|pid| Process.kill(:TERM, pid)}
end


このサンプルコードは500万回の疑似乱数の中から最も0に近い実数を見つけだすというプログラムです。sleep 0.1の部分は見なかったことにしてください。サンプルなので内容はあまり意味がありませんが、要は並列処理したい部分をMonteCarloのdiceの中に書いてあげればいいわけです。このプログラムは以下のように並列処理数を指定して実行します。

$ ruby montecarlo.rb 2

この例では2つのプロセスを並列で動かします。最近のデュアルコアのCPUでは2を指定しましょう。コアがもっと多い人は「界王拳4倍!」とか唱えながら4とかを指定するといいでしょう。コアが1つしかない方はヤムチャの気分になってあきらめてください。というのは冗談で、コア数を超えていても一応動きますので試してみてください。


メインのプロセスはforkで生成された子プロセス2つに命令を送り、待つためのスレッドがそれぞれ生成され、joinで終了を待ちます。


結果はQueueに格納され、これを個数分popして取り出し、その中から最小値を取り出して表示して終了します。Queueは便利ですね。


以下はベンチマークです。平均とかは取っていないのでかなり適当ですが。それにしてもやっぱりCore i7は速いですね。こんなことにしかこのPCを活かせていないのが悲しいですが。


Ruby1.8.7

プロセス数 CoreDuo Core i7
1 7.284s 4.482s
2 3.882s 2.120s
3 3.898s 1.417s
4 3.876s 1.102s
5 3.695s 1.163s
6 3.754s 0.997s
7 3.716s 0.958s
8 3.666s 0.974s
9 3.738s 1.029s
10 3.713s 0.983s
11 3.905s 0.981s
12 3.776s 0.952s
13 3.697s 0.934s
18 3.816s 0.896s
32 3.669s 0.907s
64 4.036s 0.942s
96 4.643s 0.973s


Ruby1.9.1

プロセス数 CoreDuo Core i7
1 4.675s 2.082s
2 2.677s 1.112s
3 2.656s 0.791s
4 2.609s 0.625s
5 2.746s 0.705s
6 2.649s 0.656s
7 2.581s 0.641s
8 2.626s 0.616s
9 2.691s 0.646s
10 2.617s 0.632s
11 2.619s 0.629s
12 2.573s 0.633s
16 2.705s 0.639s
32 2.619s 0.662s
64 3.144s 0.625s
96 3.010s 0.797s


なお、このプログラムはlinuxでしか試していませんので、動かなかった場合はがんばって動くようにしてもらえると助かります。また、MacRubyな方はこんなことしなくても普通にスレッドで動かせばよいらしいです。(くやしー!)