Threadを使ったGeneratorを作ってみた
扱うデータ数が多いと若干性能が気になる感じのGeneratorですが、callccじゃなくてThreadを使えばちょっとは早くなったりするんじゃね?と思って作ってみました。next?とnextだけ、Queueを使ってさくっと実装。
require 'thread' # スレッドを使ったGenerator class ThreadUseGenerator def initialize( enum, buff_size=nil ) @alive = true @alive_mutex = Mutex.new @q = buff_size ? SizedQueue.new( buff_size ) : Queue.new @end = Object.new @t = Thread.fork { begin enum.each {|item| break unless @alive_mutex.synchronize { @alive } @q << item } ensure @q << @end end } Thread.pass @has_next = true inner_next end def next? @has_next end def next raise "illegal state." unless next? begin @next_element ensure inner_next end end # 列挙を途中でキャンセルする。 def close @alive_mutex.synchronize { @alive = false } @q.clear @has_next = false @t.join end private def inner_next item = @q.pop @has_next = false if @end.equal? item @next_element = item end end
generatorと同じような感じで使えます。
array = ["1", "2", "3"] g = ThreadUseGenerator.new( array ) while g.next? puts g.next end
実行結果です。
1 2 3
ベンチマーク結果
気になるベンチマークは以下。
require 'benchmark' require 'thread' require 'generator' array = [].fill(1, 0..10000) Benchmark.bm(20) {|x| # 通常の列挙 x.report("each : ") { array.each {|i| } } # スレッド版Generator / バッファサイズ制限なし x.report("thread-use : ") { g = ThreadUseGenerator.new( array ) while g.next? g.next end } # スレッド版Generator / バッファサイズ100 x.report("thread-use 100 : ") { g = ThreadUseGenerator.new( array, 100 ) while g.next? g.next end } # スレッド版Generator / バッファサイズ 1000 x.report("thread-use 1000 : ") { g = ThreadUseGenerator.new( array, 1000 ) while g.next? g.next end } # generator x.report("generator : ") { g = Generator.new( array ) while g.next? g.next end } }
実行結果です。(※昨日とは別のマシンです。)
user system total real each : 0.015000 0.000000 0.015000 ( 0.003000) thread-use : 0.063000 0.000000 0.063000 ( 0.065000) thread-use 100 : 1.328000 0.000000 1.328000 ( 1.325000) thread-use 1000 : 1.219000 0.000000 1.219000 ( 1.227000) generator : 2.094000 0.047000 2.141000 ( 2.136000)