dRubyで並列処理
(※12月の1日から25日まで、日替わりで Ruby の Tips を紹介するイベント、 Ruby Advent Calendar jp: 2009 の 11 日目です。昨日は no6v さんでした。明日は id:willnet さんの予定です。)
RubyのThreadは時分割なので並列処理を行いたいときにちょっと困ります。そんなときにはdRubyを使ってみるのはいかがでしょうか。
#!/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を活かせていないのが悲しいですが。
プロセス数 | 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な方はこんなことしなくても普通にスレッドで動かせばよいらしいです。(くやしー!)