しょんぼり技術メモ

まいにちがしょんぼり

MessagePack-RPC for Rubyででかいデータをやりとりするときの性能についての簡単な調査

をやってみた。環境はLinux 2.6.35.6, Core2 Quad Q6600, 4GB RAM.

$ ruby --version
ruby 1.8.6 (2010-02-05 patchlevel 399) [x86_64-linux]

結果

結果から先に。

$ ruby mprpc-bench.rb 127.0.0.1 12345 10
********** Local Call *********
    1MB 0.00144910 ( 690.084 MBps, std-dev=0.0023, 10 trials)
    2MB 0.00340730 ( 586.975 MBps, std-dev=0.0035, 10 trials)
    4MB 0.00998870 ( 400.453 MBps, std-dev=0.0008, 10 trials)
    8MB 0.01863280 ( 429.350 MBps, std-dev=0.0000, 10 trials)
   16MB 0.02409760 ( 663.967 MBps, std-dev=0.0044, 10 trials)
   32MB 0.05156110 ( 620.623 MBps, std-dev=0.0010, 10 trials)
   64MB 0.08784970 ( 728.517 MBps, std-dev=0.0019, 10 trials)
  128MB 0.16234140 ( 788.462 MBps, std-dev=0.0039, 10 trials)

********** Sync RPC **********
    1MB 0.00576180 ( 173.557 MBps, std-dev=0.0021, 10 trials)
    2MB 0.01518030 ( 131.750 MBps, std-dev=0.0069, 10 trials)
    4MB 0.02365930 ( 169.067 MBps, std-dev=0.0058, 10 trials)
    8MB 0.05144510 ( 155.506 MBps, std-dev=0.0124, 10 trials)
   16MB 0.11360020 ( 140.845 MBps, std-dev=0.0200, 10 trials)
   32MB 0.22924020 ( 139.592 MBps, std-dev=0.0286, 10 trials)
   64MB 0.45224690 ( 141.516 MBps, std-dev=0.0326, 10 trials)
  128MB 0.82180360 ( 155.755 MBps, std-dev=0.0619, 10 trials)

********** Async RPC (Overhead of async&Future.get) **********
    1MB 0.00822520 ( 121.578 MBps, std-dev=0.0073, 10 trials)
    2MB 0.01265510 ( 158.039 MBps, std-dev=0.0062, 10 trials)
    4MB 0.01952120 ( 204.905 MBps, std-dev=0.0017, 10 trials)
    8MB 0.04721580 ( 169.435 MBps, std-dev=0.0054, 10 trials)
   16MB 0.12652830 ( 126.454 MBps, std-dev=0.0110, 10 trials)
   32MB 0.19987370 ( 160.101 MBps, std-dev=0.0264, 10 trials)
   64MB 0.42843730 ( 149.380 MBps, std-dev=0.0359, 10 trials)
  128MB 0.83951620 ( 152.469 MBps, std-dev=0.0502, 10 trials)

********** Async map RPC **********
    1MB 0.00456180 ( 219.212 MBps, std-dev=0.0002, 10 trials)
    2MB 0.00930900 ( 214.846 MBps, std-dev=0.0003, 10 trials)
    4MB 0.01869550 ( 213.955 MBps, std-dev=0.0002, 10 trials)
    8MB 0.05166050 ( 154.857 MBps, std-dev=0.0102, 10 trials)
   16MB 0.12053400 ( 132.743 MBps, std-dev=0.0090, 10 trials)
   32MB 0.24887740 ( 128.577 MBps, std-dev=0.0255, 10 trials)
   64MB 0.40526220 ( 157.922 MBps, std-dev=0.0279, 10 trials)
  128MB 0.79702430 ( 160.597 MBps, std-dev=0.0531, 10 trials)

********** Sync, threaded  RPC **********
    1MB 0.00449850 ( 222.296 MBps, std-dev=0.0003, 10 trials)
    2MB 0.01230130 ( 162.584 MBps, std-dev=0.0014, 10 trials)
    4MB 0.02656580 ( 150.570 MBps, std-dev=0.0023, 10 trials)
    8MB 0.05622410 ( 142.288 MBps, std-dev=0.0064, 10 trials)
   16MB 0.11557440 ( 138.439 MBps, std-dev=0.0095, 10 trials)
   32MB 0.26488060 ( 120.809 MBps, std-dev=0.0361, 10 trials)
   64MB 0.59101020 ( 108.289 MBps, std-dev=0.0729, 10 trials)
  128MB 1.19793390 ( 106.851 MBps, std-dev=0.1309, 10 trials)

相対比較

Localを1としたときの各手法のスループット

size(MB) Local Sync Async map thread
1 1.000 0.252 0.176 0.318 0.322
2 1.000 0.224 0.269 0.366 0.277
4 1.000 0.422 0.512 0.534 0.376
8 1.000 0.362 0.395 0.361 0.331
16 1.000 0.212 0.190 0.200 0.209
32 1.000 0.225 0.258 0.207 0.195
64 1.000 0.194 0.205 0.217 0.149
128 1.000 0.198 0.193 0.204 0.136
average 1.000 0.261 0.275 0.301 0.249

テストの内容

・Local Call
nMBの文字列をdupして返す関数を呼び、その所要時間を計る。return data.dup にかかる時間。dupしないと文字列オブジェクトの参照を見に行くだけなので比較にならない。


・Sync RPC
nMBの文字列を返す関数を同期RPCで呼び、その所要時間を計る。mpclient.call(:get_data) にかかる時間。


・Async RPC (Overhead of async&Future.get)
nMBの文字列を返す関数を非同期RPCで呼び、直後にその結果を取得。その所要時間を計る。{ future=mpclient.call_async(:get_data); future.get} にかかる時間。正しくないFutureの使い方の例。


・Async map RPC
1MBの文字列を返す関数を非同期RPCでn回呼び、その所要時間を計る。一通りmpclient.call_async(:get_data)して、future.get で結果を取得するのにかかる時間。


・Sync, threaded RPC
n個のスレッドを生成し、1MBの文字列を返す関数をそれぞれ呼ぶ。その所要時間を計る。スレッド生成→threads.each{|t| t.join} にかかる時間。

で?

やるじゃんMessagePack-RPC。非同期RPC便利ねーっていう。

クライアントの使い回しについて

各でやる場合、各スレッドごとにMessagePack::RPC::Clientインスタンスを生成しないとエラーで死ぬっぽい。
call_asyncでは死んだことないけど、call_asyncが戻ってくる前に別のスレッドがcall_asyncすると死ぬかも。
なので、スレッド使わずにmapでやるのが良さそうです。

mpclient = MessagePack::RPC::Client.new(ipaddr, port)                             
mpclient.timeout = 10                                                             
                                                                                  
sizes.each do |size|                                                              
  mpclient.call(:prepare_data, 1) # server responds 1MiB data                     
                                                                                  
  ret_array = Array.new(size).map{                                              
    mpclient.call_async(:get_data)                                              
  }.map{|f|                                                                     
    f.get                                                                       
  }                                                                             
                                                                                  
  ret_array.join                                                                
end