LoginSignup
1
0

More than 5 years have passed since last update.

native addonによる応答のバイナリ化について

Last updated at Posted at 2016-12-25

この記事は Node.js Advent Calendar 2016 25日目の記事です。
遅くなり申し訳ありません。

目的

ゲームのサーバとしてNode.jsを使っていたのですが、応答のバイナリ化についてnative addonでどれだけ処理が改善できるのかを調査してみました。

Structure

ゲーム開発界隈はC,C++,C#と脈々とC系の言語が続いていて、それこそ大昔はサーバもC++で書いていたりしていました。サーバ・クライアントという異なるターゲット間で通信で受け渡すデータ構造を合わせるために構造体を上手いこと設計し、ヘッダをサーバクライアント共用にするといったこともやっていました。気を付けることは主にpaddingだけの話なんですが、昔はendianの問題とかもあったりとか…
これ自体まぁ面白くはあるのですが要点一つ掴めば誰でもできるし、あまりこういうこと自体特殊技能にするのも良くない。もうjsonでいいじゃないか、バイナリにこだわるならmsgpackでいいじゃないか、と思うようになったのが5年前位?Node.jsの存在を知ったのもそれからちょっとしてからだったかと思います。

さてそれから幾年月。Node.jsでサーバを書きながらここ1年悩んでいたのは応答データのシリアライズの重さ。この主因に応答データの大きさがありました。
しかしゲームならでわかもしれませんが、本当にゲームの通信って1API辺り応答がデカいデータになりがちです。
ここら辺ぶっちゃけ個人的意見はあるのですが、サーバの方で今あるものをどうしようと分析結果を眺めながら、1周回ってnative addonで構造体設計してバイナリ化するのが一番いいのかなと、思うようになりました。

そこで、native addonでバイナリを作ることで実際変換パフォーマンスがどれくらい改善するのか調査してみました。

対象

題材として以下のようなデータを作ってみました。

test.js
var logindata = {
  player: {
    id: "550e8400-e29b-41d4-a716-446655440000",
    name: "test",
    exp: 1
  },
  items: [{id:"550e8400-e29b-41d4-a716-446655440000", itemid:1, num: 1}]
};

itemsの長さは可変です。これを構造体にするならこんな感じ?

hello.cc
   class LOGINDATA
    {
public:
        PLAYERDATA player;
        int  num_item;
        ITEMDATA items[1];
    };
    class ITEMDATA
    {
public:
         char id[64];
         int itemid;
         int num;
    };
    class PLAYERDATA
    {
public:
        char id[64];
        char name[32];
        int  exp;
    };

以下のように変換のロジックを書いてみました。

hello.cc
NAN_METHOD(Serialize)
{
  Isolate* isolate = Isolate::GetCurrent();

  Handle<Object> login = Handle<Object>::Cast(info[0]);

  Handle<Value> player_ = login->Get(String::NewFromUtf8(isolate, "player"));
  Handle<Value> items_  = login->Get(String::NewFromUtf8(isolate, "items"));
  Handle<Object> player = Handle<Object>::Cast(player_);
  Handle<Array> items = Handle<Array>::Cast(items_);

  size_t sizeof_buf = sizeof(LOGINDATA) + sizeof(ITEMDATA)*items->Length();
  char* buf = new char[sizeof_buf];

  LOGINDATA* logindata = (LOGINDATA*)buf;
  player->Get(String::NewFromUtf8(isolate, "id"))->ToString()->WriteUtf8(logindata->player.id);
  player->Get(String::NewFromUtf8(isolate, "name"))->ToString()->WriteUtf8(logindata->player.name);
  logindata->player.exp = player->Get(String::NewFromUtf8(isolate, "exp"))->Uint32Value();

  logindata->num_item = items->Length();
  for (unsigned int ii=0; ii<items->Length(); ii++) {
    Handle<Object> item = Handle<Object>::Cast(items->Get(ii));
    item->Get(String::NewFromUtf8(isolate, "id"))->ToString()->WriteUtf8(logindata->items[ii].id);
    logindata->items[ii].itemid = item->Get(String::NewFromUtf8(isolate, "itemid"))->Uint32Value();
    logindata->items[ii].num = item->Get(String::NewFromUtf8(isolate, "num"))->Uint32Value();
  }

  info.GetReturnValue().Set(Nan::CopyBuffer((char*)buf, sizeof_buf).ToLocalChecked());
}

測定

以下のような測定コードを作成しました。

var addon = require('./build/Release/addon');

var logindata = {
  player: {
    id: "550e8400-e29b-41d4-a716-446655440000",
    name: "test",
    exp: 1
  },
  items: []
};

var test = function(cnt, use_addon) {
  for(var ii = 0; ii < cnt; ii++) {
    logindata.items.push({id:"550e8400-e29b-41d4-a716-446655440000", itemid:ii, num: ii})
  }
  s = addon.Serialize(logindata);
  console.log(s.length);

  console.time('loop');
  for(var ii = 0; ii < 1000; ii++) {
    if (use_addon) {
      s = addon.Serialize(logindata);
    } else {
      s = JSON.stringify(logindata);
    }
  }
  console.timeEnd('loop');
}

// 各item数によるパフォーマンスの差を比較する
var sample = [100,200,300,400,500,600,700,800,900,1000];
for (var ii=0; ii< sample.length; ii++) {
//  test(sample[ii], true);
  test(sample[ii], false);
}

結果。

num items JSON.stringify native addon
100 34.292ms 81.262ms
200 90.983ms 233.420ms
300 179.845ms 470.435ms
400 303.717ms 782.321ms
500 453.410ms 1166.975ms
600 646.167ms 1675.138ms
700 854.502ms Killed...
800 1111.940ms
900 1397.911ms
1000 1699.615ms

native addonが途中で失敗しましたが、失敗を改善するまでもなくnative addonの方が重い!?
多分開放とか色々エラーが出ているのとnative addonの使い方が良くないのでしょうが…いやしかしちょっと夢がない…

結論

メリットとしてはクライアント側のデシリアライズ負荷は無くなるかと思うのですが、サーバ側のパフォーマンス改善は厳しそう、応答サイズはできるだけ小さくしてほしい…ということになってしまいました。

トリを頂いたのに夢のない結論で申し訳ないです。メリークリスマス。
もう少しコード改善して実験コード公開いたします。

※続きを書きました
http://qiita.com/takeswim/items/693a10428f6a8ad9980b

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0