404 Blog Not Found:ruby & perl - 軽量プロセスをthreadで代用 にあるスクリプトで遊びながら、ふと、明示的にスレッドを切り替えた(つまり、答えを早く出せる順に並ぶ)場合も見てみたくなって次のようにしたところ、id:sumim:20070513:p1 で軽く触れた、
ちなみに Ruby ではどうか…と試してみようと思ったのですが、(同じことをするのにかかる時間の)桁が4つほど違った
このことの“犯人”が Thread.pass なのだと判明したのでメモ。
使用した async.rb はこんな感じ。小飼さんのスクリプトを簡素化しています。
require 'thread' ary= [1,5,10,15,20,25].sort!.reverse! @be_pass = false def fib(n) Thread.pass if @be_pass if n < 3 then 1 else fib(n-2) + fib(n-1) end end def time_to_run(&blk) t0 = Time.now blk.call Time.now - t0 end elapsed = time_to_run do ary.each do |n| print "fib(#{n}) = #{fib(n)}\n" end end print "Sync ver took #{elapsed} seconds\n\n" [false, true].each do |b| @be_pass = b elapsed = time_to_run do q = Queue.new t = Thread.new { ary.size.times { puts q.shift } } ary.each do |n| Thread.new { q.push("fib(#{n}) = #{fib(n)}\n") } end t.join end print @be_pass ? "Explicit" : "Implicit" print "-switching async ver took #{elapsed} seconds\n\n" end
出力は次の通り。環境は 1.0 Ghz PowerPC、OS X。
fib(25) = 75025 fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 Sync ver took 1.280229 seconds fib(10) = 55 fib(15) = 610 fib(5) = 5 fib(1) = 1 fib(20) = 6765 fib(25) = 75025 Implicit-switching async ver took 1.316305 seconds fib(1) = 1 fib(5) = 5 fib(10) = 55 fib(15) = 610 fib(20) = 6765 fib(25) = 75025 Explicit-switching async ver took 21.583202 seconds
例によって、上のスクリプトを Squeak Smalltalk(Squeak3.9)にほぼ直訳して評価した場合、
| array bePass outStream fib elapsed | array := #(1 5 10 15 20 25) sort reversed. bePass := {false}. outStream := String new writeStream. fib := [:n | bePass first ifTrue: [Processor yield]. n < 3 ifTrue: [ 1 ] ifFalse: [ (fib fixTemps copy value: n-2) + (fib fixTemps copy value: n-1) ] ]. elapsed := [ array do: [:n | outStream cr; nextPutAll: ('fib({1}) = {2}' format: {n. fib fixTemps copy value: n}) ] ] timeToRun / 1.0e3. outStream cr; nextPutAll: ('Sync ver took {1} seconds' format: {elapsed}); cr. {false. true} do: [:bool | bePass at: 1 put: bool. elapsed := [ | queue | queue := SharedQueue new. array do: [:n | [queue nextPut: ('fib({1}) = {2}' format: {n. fib fixTemps copy value: n})] fixTemps fork]. [array size timesRepeat: [outStream cr; nextPutAll: (queue next)]] forkAndWait ] timeToRun / 1.0e3. outStream cr; nextPutAll: (bePass first ifTrue: ['Explicit'] ifFalse: ['Implicit']). outStream nextPutAll: ('-switching async ver took {1} seconds' format: {elapsed}); cr ]. World findATranscript: nil. Transcript show: outStream contents
後の両者(明示的にスレッド切り替えをしなかった場合とした場合)の間には、ほとんど差はでないようです。
fib(25) = 75025 fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 Sync ver took 0.597 seconds fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 fib(25) = 75025 Implicit-switching async ver took 0.574 seconds fib(1) = 1 fib(5) = 5 fib(10) = 55 fib(15) = 610 fib(20) = 6765 fib(25) = 75025 Explicit-switching async ver took 0.634 seconds'
えーと、念のため。上の Squeak Smalltalk のスクリプトにおいて、なぜだか bePass の値が配列の要素になっているとか、ブロックの評価やフォーク(スレッド作成)時に、ひたすら fixTemps とか fixTemps copy していたりするのは、Smalltalk であるにもかかわらず、ブロックがクロージャになっていない Squeak Smalltalk での苦肉の策なのでどうか笑わないでやってください(^_^;)。って、ならばほかの Smalltalk で書けよって話もありますが。w
rubyco さんの最新の記事 rubyco(るびこ)の日記 - JRubyを使ってFizz-Buzz問題を解く で、噂の JRuby が思いのほか簡単に使えそうだということがわかったので、手元の CRuby たちはちょっと古いバージョンですが Ruby 1.8 vs Ruby 1.9 vs JRuby 対決。
$ ruby -v ruby 1.8.5 (2006-08-25) [powerpc-darwin8.7.0] $ ruby async.rb fib(25) = 75025 fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 Sync ver took 0.889433 seconds fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 fib(20) = 6765 fib(25) = 75025 Implicit-switching async ver took 1.022443 seconds fib(1) = 1 fib(5) = 5 fib(10) = 55 fib(15) = 610 fib(20) = 6765 fib(25) = 75025 Explicit-switching async ver took 28.118235 seconds
$ ruby1.9 -v ruby 1.9.0 (2007-03-23 patchlevel 0) [powerpc-darwin8.8.0] $ ruby1.9 async.rb fib(25) = 75025 fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 Sync ver took 0.110057 seconds fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 fib(25) = 75025 Implicit-switching async ver took 0.177561 seconds fib(1) = 1 fib(5) = 5 fib(10) = 55 fib(15) = 610 fib(20) = 6765 fib(25) = 75025 Explicit-switching async ver took 1.206743 seconds
$ jruby -v ruby 1.8.5 (2007-05-16 rev 3672) [ppc-jruby1.0.0RC2] $ jruby async.rb fib(25) = 75025 fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 Sync ver took 1.192 seconds fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 fib(25) = 75025 Implicit-switching async ver took 1.106 seconds fib(1) = 1 fib(5) = 5 fib(10) = 55 fib(15) = 610 fib(20) = 6765 fib(25) = 75025 Explicit-switching async ver took 2.494 seconds
Ruby 1.8 の Thread.pass の遅さの異常性が際だちます。あと、Squeak Smalltalk が Ruby 1.9 にあっさり負かされているように見えるのが悔しいので、次のなりふり構わない版に替えて対抗を試みます(^_^;)。
Smalltalk at: #BEPASS put: false
Integer compile: 'fib BEPASS ifTrue: [Processor yield]. ^self < 3 ifTrue: [ 1 ] ifFalse: [ (self-2) fib + (self-1) fib ]'
| array outStream elapsed | array := #(1 5 10 15 20 25) sort reversed. BEPASS := false. outStream := String new writeStream. elapsed := [ array do: [:n | outStream cr; nextPutAll: ('fib({1}) = {2}' format: {n. n fib}) ] ] timeToRun / 1.0e3. outStream cr; nextPutAll: ('Sync ver took {1} seconds' format: {elapsed}); cr. {false. true} do: [:bool | BEPASS := bool. elapsed := [ | queue | queue := SharedQueue new. array do: [:n | [queue nextPut: ('fib({1}) = {2}' format: {n. n fib})] fixTemps fork]. [array size timesRepeat: [outStream cr; nextPutAll: (queue next)]] forkAndWait ] timeToRun / 1.0e3. outStream cr; nextPutAll: (BEPASS ifTrue: ['Explicit'] ifFalse: ['Implicit']). outStream nextPutAll: ('-switching async ver took {1} seconds' format: {elapsed}); cr ]. World findATranscript: nil. Transcript show: outStream contents
ブロックで実装していた fib をメソッド「Integer >> #fib」に変更(これで再帰呼び出しが素直に書けます…)。bePass フラグは受け渡し方法を考えるのが面倒だったのでグローバル変数「BEPASS」にしちゃいました。ごめんなさい。
fib(25) = 75025 fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 Sync ver took 0.056 seconds fib(25) = 75025 fib(20) = 6765 fib(15) = 610 fib(10) = 55 fib(5) = 5 fib(1) = 1 Implicit-switching async ver took 0.068 seconds fib(1) = 1 fib(5) = 5 fib(10) = 55 fib(15) = 610 fib(20) = 6765 fib(25) = 75025 Explicit-switching async ver took 0.123 seconds