Ractor でフィボナッチ数列

まずは小手調べ

takeするまで待って、takeするたびに無限に同じオブジェクトを返してくれる Ractor は、「Pull型通信」を使ってこんな風に作れる。

r = Ractor.new do
  loop { Ractor.yield 1 }
end

Array.new(5) { r.take }    #=>[1, 1, 1, 1, 1]

わかりやすいですね。

では、例えば単調増加列も簡単に作れる。

r = Ractor.new do
  i = 1
  loop do
    Ractor.yield i
    i += 1
  end
end

Array.new(5) { r.take }    #=>[1, 2, 3, 4, 5]

 

フィボナッチ数列を Ractor で計算してみる

こんなコードを書いてみた。

def fibonacci
  fib = Array.new(N + 1)
  
  2.times do |i|
    fib[i] = Ractor.new(i) do |j|
      loop { Ractor.yield j }
    end
  end
  
  (2..N).each do |i|
    fib[i] = Ractor.new(fib[i - 1], fib[i - 2]) do |a, b|
      e = a.take + b.take
      loop { Ractor.yield e }
    end
  end
  
  fib[N].take
end

fib[n]fib[n - 1]fib[n - 2]が計算するまで待ち(これらすべて Ractor オブジェクト)、双方が計算を終了したらtakeされるのを待つ。それで、takeされるたび、計算結果を返し続ける。

ってまあ、ほとんど意味のないプログラムだが、計算と一種の「メモ化」ですね。おもしろいといえばおもしろい。パフォーマンスはどうかというと、N = 3000 としてわたしの環境で 3.4秒くらい。3001個の Ractor オブジェクトが生成されるわけだが、どうなのかな。

なお、fib[3000].takeの結果は、

4224696333392304878706725602341482782579852840250681098010280137314308584370130707224123599639141511088446087538909603607640194711643596029271983312598737326253555802606991585915229492453904998722256795316982874482472992263901833716778060607011615497886719879858311468870876264597369086722884023654422295243347964480139515349562972087652656069529806499841977448720155612802665404554171717881930324025204312082516817125

という具合です。