ようわ memcached 的な用途に druby をつかえないかなーと思って試してみたけど現実を見せつけられた格好です, というログです.

実行結果

$ ruby drbclient.rb 100
      user     system      total        real
  0.030000   0.010000   0.040000 (  0.109558)

$ ruby drbclient.rb 1000
      user     system      total        real
  1.020000   0.110000   1.130000 (  3.246166)

$ ruby drbclient.rb 10000
      user     system      total        real
 90.040000   4.640000  94.680000 (239.860364)

drbserver.rb – druby サーバ

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

drbclient.rb – druby クライアント

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}" }
}

試した感じ, オブジェクト数の増加とともに指数関数的なパフォーマンス劣化が生じる模様です.

追記 6/12(1:02)

druby 作者である id:m_seki さんご本人にご指摘をいただいたので見直したところ, サーバ側のフロントオブジェクトが Array で, かつ, << メソッドを使っているために遅いんだよ, と教えていただきました. 関さん, ありがとうございます.

理由として, 上記のコードの場合

  • フロントオブジェクトを値渡ししている
  • << メソッドの戻り値が self なので, << メソッドを呼び出すたびにサーバ→クライアントへフロントオブジェクトのコピーを渡してしまう
  • 結果 O(N2) となる*1

という動きになっていたようです. 値渡しがこういう現象につながるとは想像がおよびませんでした.

というわけで, フロントオブジェクトを値渡し→参照渡しへ修正するとリニアな結果になりました. サーバ側は下記に記載している drbserver2.rb, クライアント側は上記の drbclient.rb です.

$ ruby drbclient.rb 100
      user     system      total        real
  0.030000   0.000000   0.030000 (  0.102600)

$ ruby drbclient.rb 1000
      user     system      total        real
  0.340000   0.060000   0.400000 (  1.136783)

$ ruby drbclient.rb 10000
      user     system      total        real
  3.250000   0.570000   3.820000 ( 11.807745)
drbserver2.rb – druby サーバ参照渡し版
require "drb/drb"
front = []
front.extend DRbUndumped                       # ここ追加
DRb.start_service("druby://:8888", front)
puts DRb.uri
sleep

ほかのやりかたについては, この件に対する関さんの解説ページ参照渡しと値渡しに書いてあります.

*1:サーバ側で Marshal.dump(Array), クライアント側で Marshal.load(Array) で N2 と理解しました