読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Threadを使ったGeneratorを作ってみた

Ruby

扱うデータ数が多いと若干性能が気になる感じの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)
  • 普通に列挙する場合と比べるとだいぶ遅いけど、callcc版よりはましかな。
  • バッファサイズ(イテレータから一度に読み込むデータ数)を増やすと早くなるけど、あんまり増やすとバッファ内のデータ分だけメモリを消費してイテレータの意味がなくなってくるので注意。