@m_seki の

I like ruby tooから引っ越し

値渡しの罠

オンメモリキャッシュサーバとして druby を試してみた→失敗 - yagihiro outputでなにが起きてるか。

require "drb/drb"
front = []
DRb.start_service("druby://:8888", front)
puts DRb.uri
sleep
require "drb/drb"
require "benchmark"

DRb.start_service
there = DRbObject.new_with_uri "druby://:8888"

puts Benchmark::CAPTION
puts Benchmark::measure {
  ARGV[0].to_i.times {|i| there << "hello world #{i}" }
}

唯一のRMIは there << "hello world #{i}"ここ。thereはリモートのArrayなので、Array#<<が呼び出される。<<は要素を追加してselfを返すので、Arrayが戻り値。
サーバ側のfrontのArrayはMarshal可能なので戻り値は値渡し(コピー)となり、O(N)の繰り返しでO(N)なのでO(N2)になってしまう。

じゃあどすればよいかというと、frontでDRbUndumpedをextendするか、<<ではなく[]=を使うかするとかかなあ。

require "drb/drb"
require "benchmark"

DRb.start_service
there = DRbObject.new_with_uri "druby://:8888"

puts Benchmark::CAPTION
puts Benchmark::measure {
  ARGV[0].to_i.times {|i| there[i] = "hello world #{i}" }
}

こんな風にするとリニアっぽい値が出た。

$ ruby b.rb 100
      user     system      total        real
  0.010000   0.010000   0.020000 (  0.057440)
$ ruby b.rb 1000
      user     system      total        real
  0.110000   0.050000   0.160000 (  0.543706)
$ ruby b.rb 10000
      user     system      total        real
  1.080000   0.520000   1.600000 (  5.447686)

memcachedと比較するならサーバ側はHashが良いんじゃないかと思うんだけど、どうなんだろう。その場合でもクライアントは変更なしでサーバをこうするだけ。

require "drb/drb"
front = {}
DRb.start_service("druby://:8888", front)
puts DRb.uri
sleep

結果は似たようなものだった。

$ ruby b.rb 100
      user     system      total        real
  0.010000   0.010000   0.020000 (  0.067852)
$ ruby b.rb 1000
      user     system      total        real
  0.110000   0.060000   0.170000 (  0.523545)
$ ruby b.rb 10000
      user     system      total        real
  1.080000   0.520000   1.600000 (  5.254644)

まあ、なんで脊髄反射的に原因がわかったかというと、前に自分でもやったことがあるからなんだけどね。
集合への操作はその戻り値をチェックしといた方がよいです > dRuby。結果のクローンが届いてしまってがっかりすることが多いので。