目的
「ゲームのサーバとしてNode.jsを使っていたのですが、応答のバイナリ化についてnative addonでどれだけ処理が改善できるのか」を証明しようと意気込んだadvent calendar向け実験した結果が余りに夢がなかったので、今年のうちにもう少し夢を追ってみることにしました。
追試
この実験からさらにパフォーマンスを上げる方向で調整し、ここまで改善しました。
- v8::Object::Get()で使用するkeyをあらかじめ用意する
- v8::String::WriteUtf8()が重いのでASCII文字が担保されている文字列はWriteOneByte()に任せる(UUIDとか)
そもそもサンプルデータの作り方もよくなかったのでJSON.stringifyの方の処理時間も下がっていますが、native addonが重い状況は変わりません。以前の2倍超よりは収まっているようです。
num items | JSON.stringify | native addon |
---|---|---|
100 | 34.342ms | 53.611ms |
200 | 62.441ms | 105.324ms |
300 | 93.283ms | 158.426ms |
400 | 122.436ms | 199.763ms |
500 | 158.034ms | 263.912ms |
600 | 186.730ms | 323.354ms |
700 | 213.992ms | 378.184ms |
800 | 242.050ms | 474.247ms |
900 | 270.003ms | 451.293ms |
1000 | 305.387ms | 494.912ms |
どこがボトルネックになっているのか一つ一つ処理を落としながら眺めていたのですが、どうもv8::Object::Get()
が思いのほか重い。これは引数でObjectを渡しているのが良くなさそうです。
PLAYERDATA,ITEMDATAそれぞれバイナリ化する機能を分け引数の渡し方も文字か数値のみにするようにすれば、もう少し改善できるのでは…とも思いましたが、そうすると最終的にPLAYERDATA,ITEMDATAを連結しLOGINDATAを作るのはjs側になり「LOGINDATAをnative addonで作る」という趣旨から離れてしまいます。
現状は現状で1call辺り1msec以下で十分軽量であること、応答したバイナリはクライアントでデシリアライズすることなく素で使えること(=サーバは負荷増だがクライアントサイドのCPUリソース負荷軽減)を考えると、これはこれでよいのではないかとも思います。
addonの実装を最小単位にしてみる
ということで趣旨から離れてPLAYERDATA,ITEMDATA変換をaddonで実装しjs側で連結する、という手法を試してみました…が、結果はnative addonで全実装するよりも遅くなりました。
追いかけてみたところ返り値のBufferを作るNan::CopyBuffer((char*)buf, sizeof_buf)
ここもまた重いポイントになっていました。最小単位にしてもアイテム数分callされることになり、結果として時間がかかるようになったと思われます。
| num items | use minimal addon |
|:-:|:-:|:-:|
| 100 | 118.767ms |
| 200 | 194.401ms |
| 300 | 279.565ms |
| 400 | 374.365ms |
| 500 | 464.634ms |
| 600 | 580.934ms |
| 700 | 660.586ms |
| 800 | 756.732ms |
| 900 | 844.234ms |
| 1000 | 938.798ms |
breによるバイナリ化
趣旨から離れるのであれば、コメントでいただいたbreによるバイナリ化でnode.js側で実装することも選択になると思います。こちらについても実装してみました…が、結論としては処理に時間がかかりそうでした。
| num items | use bre |
|:-:|:-:|:-:|
| 100 | 1135.328ms |
| 200 | 2135.037ms |
| 300 | 3152.748ms |
| 400 | 4175.101ms |
| 500 | 5222.016ms |
| 600 | 6201.442ms |
| 700 | 7396.662ms |
| 800 | 32462.285ms |
| 900 | 36018.517ms |
| 1000 | 39275.021ms |
defineObjectRecord(),view()値の書き込みに時間がかかっている印象でした。
使い方が良くないかもしれません。
結論
- 引数がObjectの場合、値取得のアクセスが重くなる。
- 応答バイナリはBufferで返す必要があり、この生成も重い。応答データはなるべく1つの関数内で作成を済ませたほうが良い。かつなるべく引数はObjectで渡さないほうが良い。
- サーバのCPUリソースを使ってクライアントのCPUリソースを空ける、という意味では、native addonでの応答バイナリ化は良い選択かもしれない。